{"openapi":"3.1.0","info":{"title":"OptAlgo API","version":"1","description":"The OptAlgo API for agents (Claude Code, Codex) and scripts: read the account, run personal bots, send signals, read results, run engine backtests (/v1/backtests). Authenticate with the X-API-Key header - create a key in OptAlgo → AI agents, or let an agent log in with POST /v1/agent/login so the key is never pasted into a chat. Scopes: read = read the account, bots and results; backtest = run engine backtests of your own strategy code (check + submit), nothing else; bots = manage bots and trade them on paper; trade = live trading: signals and closes on live bots, switching to live, creating live bots, webhook keys. A missing scope is 403 scope_required with required_scope and fix_url - a link the owner opens to turn that scope on for this key. Errors are {\"error\": <code>, \"detail\": <message>}. /v1 only gains fields and routes; a breaking change ships as /v2 with 90 days of overlap. Guide for agents: https://docs.optalgo.com/optalgo-llm.md"},"servers":[{"url":"https://api.optalgo.com"}],"tags":[{"name":"Account"},{"name":"Exchanges"},{"name":"Bots"},{"name":"Multi-symbol"},{"name":"Signals"},{"name":"Results"},{"name":"Backtests"},{"name":"Hosted strategies"},{"name":"Tickets"},{"name":"Connect"},{"name":"Agent login"}],"paths":{"/v1/me":{"get":{"summary":"Me","description":"The account behind the key: plan, multi-symbol limits, the key itself (scopes, autonomy, limits, pause),\ntickets left today, strike_count {used, max} (strikes of forbidden strategy code; at max API access is\nsuspended), the webhook URL and - when the key has a daily loss limit - today's live realized PnL.","operationId":"me_v1_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["Account"],"security":[{"ApiKeyAuth":[]}]}},"/v1/exchanges":{"get":{"summary":"Exchanges","description":"Active exchanges with their account types, and whether the user has connected each one.","operationId":"exchanges_v1_exchanges_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["Exchanges"],"security":[{"ApiKeyAuth":[]}]}},"/v1/exchanges/{exchange_id}/symbols":{"get":{"summary":"Exchange Symbols","description":"Tradable symbols of one exchange account type. `id` is what a bot's symbol and a signal's ticker use\n(BTCUSDT.P = futures, BTCUSDT = spot).","operationId":"exchange_symbols_v1_exchanges__exchange_id__symbols_get","parameters":[{"required":true,"schema":{"type":"string","title":"Exchange Id"},"name":"exchange_id","in":"path"},{"required":true,"schema":{"type":"string","title":"Account Type"},"name":"account_type","in":"query"},{"required":false,"schema":{"type":"string","title":"Q"},"name":"q","in":"query"},{"required":false,"schema":{"type":"integer","maximum":500.0,"minimum":1.0,"title":"Limit","default":50},"name":"limit","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Exchanges"],"security":[{"ApiKeyAuth":[]}]}},"/v1/prices":{"get":{"summary":"Prices","description":"The live price of up to 20 symbols, in the order paper fills use (services/live_prices.py): freedx-data's\nprice book first, the exchange ticker only when data has no fresh price (5 s):\n{data: {prices: [{exchange, symbol, price, at, source: data | exchange}][, unavailable: [symbol]]}, next}.\nprice = the last trade (else the bid / ask mid), at = when it was last confirmed (ISO UTC). An agent never\nneeds a price from the web: a signal's stop loss / take profit without entry_price is measured from this price.\n422 invalid_request (every bad symbol at once, in findings), 503 price_unavailable when none has a fresh price.","operationId":"prices_v1_prices_get","parameters":[{"description":"Comma-separated tickers: BTCUSDT.P (perpetual), BTCUSDT (spot). At most 20.","required":true,"schema":{"type":"string","maxLength":1000,"title":"Symbols","description":"Comma-separated tickers: BTCUSDT.P (perpetual), BTCUSDT (spot). At most 20."},"name":"symbols","in":"query"},{"description":"BINANCE (default) or BYBIT","required":false,"schema":{"type":"string","maxLength":32,"title":"Exchange","description":"BINANCE (default) or BYBIT","default":"BINANCE"},"name":"exchange","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Exchanges"],"security":[{"ApiKeyAuth":[]}]}},"/v1/connections":{"get":{"summary":"Connections","description":"The user's exchange connections (never their keys). Connecting an exchange is done in the app.","operationId":"connections_v1_connections_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["Exchanges"],"security":[{"ApiKeyAuth":[]}]}},"/v1/bots":{"get":{"summary":"List Bots","description":"The user's personal bots (\"My Bots\"), newest first; a multi-symbol bot lists its tickers as children.","operationId":"list_bots_v1_bots_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["Bots"],"security":[{"ApiKeyAuth":[]}]},"post":{"summary":"Create Bot","description":"single: a bot on one exchange symbol (exchange_id, account_type, symbol, allocated_amount; paper by\ndefault). auto: a paper bot its first signal configures. multi: a multi-symbol bot (max_positions,\nallocated_amount, optional account_type) - its first signal sets exchange, market type and quote. A LIVE\nbot (is_paper false) needs the trade scope (403 scope_required) and its creation is announced to the user\non Telegram.","operationId":"create_bot_v1_bots_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBotBody"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Bots"],"security":[{"ApiKeyAuth":[]}]}},"/v1/bots/{bot_id}":{"get":{"summary":"Get Bot","description":"One personal bot in full: settings, mode and its recent paper <-> live flips, PnL, open trade; a\nmulti-symbol bot with its tickers. 404 for another user's bot or a system strategy.","operationId":"get_bot_v1_bots__bot_id__get","parameters":[{"required":true,"schema":{"type":"string","title":"Bot Id"},"name":"bot_id","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Bots"],"security":[{"ApiKeyAuth":[]}]},"delete":{"summary":"Delete Bot","description":"409 while the bot has an open trade: close (or stop) it first.","operationId":"delete_bot_v1_bots__bot_id__delete","parameters":[{"required":true,"schema":{"type":"string","title":"Bot Id"},"name":"bot_id","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Bots"],"security":[{"ApiKeyAuth":[]}]},"patch":{"summary":"Update Bot","description":"Only the fields sent change. 409 while the bot has an open trade (and for paper <-> live on a\nmulti-symbol bot with open tickers). max_positions cannot go below the sum of the tickers' weights (422\nweight_budget_exceeded). A paper -> live switch needs the trade scope (403 scope_required); live -> paper and\nevery other field need bots only. A changed allocation, mode or max_positions is announced to the user on\nTelegram; a paper <-> live flip is recorded in the bot's mode_history as this key.","operationId":"update_bot_v1_bots__bot_id__patch","parameters":[{"required":true,"schema":{"type":"string","title":"Bot Id"},"name":"bot_id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateBotBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Bots"],"security":[{"ApiKeyAuth":[]}]}},"/v1/bots/{bot_id}/start":{"post":{"summary":"Start Bot","description":"Start (activate) the bot so the listener accepts its signals again. Idempotent: an active bot stays\nactive. A live bot does not start while the key is paused (403 key_paused).","operationId":"start_bot_v1_bots__bot_id__start_post","parameters":[{"required":true,"schema":{"type":"string","title":"Bot Id"},"name":"bot_id","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Bots"],"security":[{"ApiKeyAuth":[]}]}},"/v1/bots/{bot_id}/stop":{"post":{"summary":"Stop Bot","description":"Stopping a bot CLOSES its open positions (one CLOSE_ALL per open trade), as in the app.","operationId":"stop_bot_v1_bots__bot_id__stop_post","parameters":[{"required":true,"schema":{"type":"string","title":"Bot Id"},"name":"bot_id","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Bots"],"security":[{"ApiKeyAuth":[]}]}},"/v1/bots/{bot_id}/convert-to-multi":{"post":{"summary":"Convert Bot","description":"A single bot becomes the first ticker of a new multi-symbol bot; its key and TradingView alert keep\nworking. Returns the new multi-symbol bot.","operationId":"convert_bot_v1_bots__bot_id__convert_to_multi_post","parameters":[{"required":true,"schema":{"type":"string","title":"Bot Id"},"name":"bot_id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConvertBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Multi-symbol"],"security":[{"ApiKeyAuth":[]}]}},"/v1/bots/{bot_id}/children":{"get":{"summary":"List Children","description":"The tickers of a multi-symbol bot with their weight, pause state, open position, PnL and slot_amount (what\nthe next entry on that ticker is sized on). weight_sum is what max_positions bounds.","operationId":"list_children_v1_bots__bot_id__children_get","parameters":[{"required":true,"schema":{"type":"string","title":"Bot Id"},"name":"bot_id","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Multi-symbol"],"security":[{"ApiKeyAuth":[]}]}},"/v1/bots/{bot_id}/children/{ticker}":{"delete":{"summary":"Remove Child","description":"Remove a ticker from a multi-symbol bot: 409 while it has an open position (close it first). Its trades\nstay in history; a later entry signal for the ticker spawns it again with weight 1.","operationId":"remove_child_v1_bots__bot_id__children__ticker__delete","parameters":[{"required":true,"schema":{"type":"string","title":"Bot Id"},"name":"bot_id","in":"path"},{"required":true,"schema":{"type":"string","title":"Ticker"},"name":"ticker","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Multi-symbol"],"security":[{"ApiKeyAuth":[]}]},"patch":{"summary":"Update Child","description":"weight: the ticker's share of a slot (0 < weight <= max_positions; 1 = one slot). The next entry on it is\nsized budget / max_positions * weight; an open position is not resized. The weights of all tickers (unset =\n1) must add up to at most max_positions, else 422 weight_budget_exceeded with the sum they would reach.\npaused: true refuses new entries on the ticker (its exits still pass), false resumes it - only while the\nbot is active (409). Note: stopping the bot pauses every ticker and starting it resumes every ticker.\nReturns the ticker's row.","operationId":"update_child_v1_bots__bot_id__children__ticker__patch","parameters":[{"required":true,"schema":{"type":"string","title":"Bot Id"},"name":"bot_id","in":"path"},{"required":true,"schema":{"type":"string","title":"Ticker"},"name":"ticker","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChildBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Multi-symbol"],"security":[{"ApiKeyAuth":[]}]}},"/v1/bots/{bot_id}/alert-templates":{"get":{"summary":"Alert Templates","description":"Ready-to-paste TradingView alert messages for this bot (field names as freedx-signal-listener\nmodels/request_models.py reads them): entry_long, entry_short, close, close_partial, move_stops. A single\nbot's exchange and ticker are filled in; a multi-symbol bot (or a bot still waiting for its first signal)\nkeeps TradingView's {{ticker}} / {{exchange}}. strategy_key is filled in only for a key with the trade\nscope - any other key gets a placeholder to replace from the app.","operationId":"alert_templates_v1_bots__bot_id__alert_templates_get","parameters":[{"required":true,"schema":{"type":"string","title":"Bot Id"},"name":"bot_id","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Signals"],"security":[{"ApiKeyAuth":[]}]}},"/v1/bots/{bot_id}/signal":{"post":{"summary":"Send Signal","description":"Send one TradingView-style signal to the bot. 202 = handed to the signal service, NOT executed: poll\nstatus_url for the outcome (rejections are only visible there). A paper bot takes signals from a key with\nthe bots scope; a LIVE bot needs the trade scope (403 scope_required). The checks and the send are\nservices/bot_signals.validate_and_send (shared with hosted strategies).","operationId":"send_signal_v1_bots__bot_id__signal_post","parameters":[{"required":true,"schema":{"type":"string","title":"Bot Id"},"name":"bot_id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignalBody"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Signals"],"security":[{"ApiKeyAuth":[]}]}},"/v1/bots/{bot_id}/close":{"post":{"summary":"Close Position","description":"Close the bot's open position: all of it (no close_ratio, or 1) or a part (close_ratio 0..1 of the filled\nsize). A multi-symbol bot closes one ticker, or every open ticker when no ticker is given. 409 when there is\nnothing open. A paper bot is closed with the bots scope; a LIVE bot needs the trade scope (403\nscope_required).","operationId":"close_position_v1_bots__bot_id__close_post","parameters":[{"required":true,"schema":{"type":"string","title":"Bot Id"},"name":"bot_id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CloseBody"}}}},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Signals"],"security":[{"ApiKeyAuth":[]}]}},"/v1/signals/{transaction_id}":{"get":{"summary":"Signal Outcome","description":"What happened to a signal sent through /v1 (or any signal of the user carrying this transaction_id):\nreceived -> dispatched -> executed, or rejected / skipped / failed / dropped with the reason. Outcomes are\nkept 30 days.","operationId":"signal_outcome_v1_signals__transaction_id__get","parameters":[{"required":true,"schema":{"type":"string","title":"Transaction Id"},"name":"transaction_id","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Signals"],"security":[{"ApiKeyAuth":[]}]}},"/v1/trades":{"get":{"summary":"List Trades","description":"The user's trades, newest first. mode: paper / live trades only (all by default). since / until: epoch\nseconds on opened_at. Pass the last row's opened_at as `before` for the next page. format=csv streams up to\nCSV_MAX_ROWS rows (limit ignored) for spreadsheets and dashboards.","operationId":"list_trades_v1_trades_get","parameters":[{"required":false,"schema":{"type":"string","enum":["open","closed","all"],"title":"Status","default":"open"},"name":"status","in":"query"},{"required":false,"schema":{"type":"string","enum":["paper","live","all"],"title":"Mode","default":"all"},"name":"mode","in":"query"},{"required":false,"schema":{"type":"string","title":"Bot Id"},"name":"bot_id","in":"query"},{"required":false,"schema":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Limit","default":50},"name":"limit","in":"query"},{"required":false,"schema":{"type":"number","title":"Before"},"name":"before","in":"query"},{"required":false,"schema":{"type":"number","title":"Since"},"name":"since","in":"query"},{"required":false,"schema":{"type":"number","title":"Until"},"name":"until","in":"query"},{"required":false,"schema":{"type":"string","enum":["json","csv"],"title":"Format","default":"json"},"name":"format","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Results"],"security":[{"ApiKeyAuth":[]}]}},"/v1/summary":{"get":{"summary":"Summary","description":"One read for a status report: bots, open trades, PnL (overall and per bot), the monthly trade quota.\n\npnl.total is the account's PnL as the app computes it (StrategyService.get_overall_pnl: realized and\nunrealized, system-strategy subscriptions included). pnl.paper / pnl.live split the REALIZED part by the\ntrade rows' paper flag (_realized_by_mode) - they are summed from the trades collection, so they can differ\nfrom total.realized by what the app's per-strategy PnL counts differently (deleted bots, fees). Unrealized\nPnL needs live prices per open position and is not split: paper.unrealized and live.unrealized are null.","operationId":"summary_v1_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["Results"],"security":[{"ApiKeyAuth":[]}]}},"/v1/logs":{"get":{"summary":"Bot Logs","description":"The bot log feed the app shows (signals received, orders, rejections with their reason), newest first;\nevery personal bot when bot_id is left out. trade_id / transaction_id select the rows of one trade / one\nsignal (system-strategy trades of the user included) and always come with detail. detail=true adds each\nrow's steps (the listener / worker trail) and extra (the signal as received, error details; secrets\nredacted).","operationId":"bot_logs_v1_logs_get","parameters":[{"required":false,"schema":{"type":"string","title":"Bot Id"},"name":"bot_id","in":"query"},{"required":false,"schema":{"type":"string","title":"Trade Id"},"name":"trade_id","in":"query"},{"required":false,"schema":{"type":"string","title":"Transaction Id"},"name":"transaction_id","in":"query"},{"required":false,"schema":{"type":"boolean","title":"Detail","default":false},"name":"detail","in":"query"},{"required":false,"schema":{"type":"integer","maximum":200.0,"minimum":1.0,"title":"Limit","default":50},"name":"limit","in":"query"},{"required":false,"schema":{"type":"number","title":"Before"},"name":"before","in":"query"},{"required":false,"schema":{"type":"string","enum":["INFO","WARNING","ERROR"],"title":"Level"},"name":"level","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Results"],"security":[{"ApiKeyAuth":[]}]}},"/v1/tickets":{"get":{"summary":"My Tickets","description":"The user's tickets and in-app reports, newest first (100 max), with the OptAlgo team's reply.","operationId":"my_tickets_v1_tickets_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["Tickets"],"security":[{"ApiKeyAuth":[]}]},"post":{"summary":"Submit Tickets","description":"File bug reports, feature requests and questions in ONE batch (1-10 items). Each NEW item becomes its own\nticket in the OptAlgo team's inbox; at most ticket_limit_per_day new tickets per user per UTC day (registry,\ndefault 20; the answer says how many are left). An item that repeats one of the user's open tickets (same\nkind + normalized title, last TICKET_DEDUPE_DAYS days, or earlier in the same batch) is merged into it\n(extra.report.repeats + 1): it answers that ticket's fingerprint with duplicate true and counts nothing.\nA connector (optalgo-mcp report_issue) files with channel \"connector\" and at most CONNECTOR_TICKETS_PER_HOUR\nnew tickets per grant per hour (429 ticket_rate_limited) on top of the daily limit.","operationId":"submit_tickets_v1_tickets_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TicketBatch"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Tickets"],"security":[{"ApiKeyAuth":[]}]}},"/v1/bots/{bot_id}/stats":{"get":{"summary":"Bot Stats","description":"Win / loss, PnL, fees, drawdown and durations of the bot's CLOSED trades (a multi-symbol bot: its\ntickers' trades, also split per ticker), for the whole history or the last 7 / 30 / 90 days by close\ntime; `all` / `paper` / `live` split the same trades by mode. `open_positions` counts the trades open\nnow, whatever the period. Backtest trades are never counted.","operationId":"bot_stats_v1_bots__bot_id__stats_get","parameters":[{"required":true,"schema":{"type":"string","title":"Bot Id"},"name":"bot_id","in":"path"},{"required":false,"schema":{"type":"string","enum":["7d","30d","90d","all"],"title":"Period","default":"all"},"name":"period","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Results"],"security":[{"ApiKeyAuth":[]}]}},"/v1/bots/{bot_id}/equity":{"get":{"summary":"Bot Equity","description":"The bot's cumulative realized PnL series, exactly the chart the app draws for it (routes/chart.py with\nstrategy_id: trade_service.get_chart, or the merged children of a multi-symbol bot): one point per\ninterval from the first trade to now, `pnl` being the realized total at the START of that interval;\ncurrent_realized is the total now (trades closed in the current interval).","operationId":"bot_equity_v1_bots__bot_id__equity_get","parameters":[{"required":true,"schema":{"type":"string","title":"Bot Id"},"name":"bot_id","in":"path"},{"required":false,"schema":{"type":"string","enum":["1h","1d","3d"],"title":"Interval","default":"1d"},"name":"interval","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Results"],"security":[{"ApiKeyAuth":[]}]}},"/v1/equity":{"get":{"summary":"Combined Equity","description":"The combined cumulative realized PnL curve. scope my_bots: every personal bot of the user (single,\nauto and multi-symbol with their tickers) summed - the My Bots view. scope all: the dashboard curve the\napp draws (personal bots plus OptAlgo strategy subscriptions and funds, routes/chart.py without\nstrategy_id, same cache).","operationId":"combined_equity_v1_equity_get","parameters":[{"required":false,"schema":{"type":"string","enum":["1h","1d","3d"],"title":"Interval","default":"1d"},"name":"interval","in":"query"},{"required":false,"schema":{"type":"string","enum":["my_bots","all"],"title":"Scope","default":"my_bots"},"name":"scope","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Results"],"security":[{"ApiKeyAuth":[]}]}},"/v1/trades/{trade_id}":{"get":{"summary":"Get Trade","description":"One trade of the user in full - by trade_id or by the row's _id (a paper trade may have no trade_id),\nas GET /v1/logs?trade_id= accepts. Adds to the list row: exit_history (every partial / final close),\nstop_loss / take_profit / trailing, entry fill state, the PnL percentages and the duration. Exchange\nsecrets never appear in a trade row.","operationId":"get_trade_v1_trades__trade_id__get","parameters":[{"required":true,"schema":{"type":"string","title":"Trade Id"},"name":"trade_id","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Results"],"security":[{"ApiKeyAuth":[]}]}},"/v1/positions":{"get":{"summary":"Positions","description":"What the EXCHANGE holds right now: the open futures positions of every connected live exchange account,\nread from the exchange (not from OptAlgo's trades), each attributed to the user's bot when exactly one\nopen trade matches it. `paper` lists the open paper trades (no mark price: paper has no exchange).\nAn exchange account that cannot be read is an `errors` entry, never a failure of the call. Cached\n10 s per user.","operationId":"positions_v1_positions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["Exchanges"],"security":[{"ApiKeyAuth":[]}]}},"/v1/balance":{"get":{"summary":"Balance","description":"Balances of every connected live exchange account (exchange_service.get_exchange_balance_with_status, the\napp's /exchanges/balance) and of the paper exchange (10,000 USDT per exchange account type, the app's\n/paper_exchanges/balance), with what is allocated to bots and what is free for a new one. `complete` is\nfalse when an account did not answer (it is then missing from `live`). Cached 10 s per user.","operationId":"balance_v1_balance_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["Exchanges"],"security":[{"ApiKeyAuth":[]}]}},"/v1/connections/binance/oauth":{"post":{"summary":"Start Binance Oauth","description":"A Binance authorize link for the key's user. The user opens it, approves on Binance, and the API creates\nthe connection (Binance mints the API key: no secrets pass through the agent). Poll status_url.","operationId":"start_binance_oauth_v1_connections_binance_oauth_post","responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["Connect"],"security":[{"ApiKeyAuth":[]}]}},"/v1/connections/binance/oauth/{connect_id}":{"get":{"summary":"Binance Oauth Status","description":"pending -> connected (connection_id) | failed (reason, detail) | expired (link not approved in 15 min).\n404 for an unknown id, another user's, or one older than an hour.","operationId":"binance_oauth_status_v1_connections_binance_oauth__connect_id__get","parameters":[{"required":true,"schema":{"type":"string","title":"Connect Id"},"name":"connect_id","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Connect"],"security":[{"ApiKeyAuth":[]}]}},"/v1/c/{token}":{"get":{"summary":"Open a short Binance connect link","description":"The short_url of POST /v1/connections/binance/oauth, opened in a browser (no X-API-Key): 302 to the\nBinance authorize page while the link lives (15 minutes), else 404 text/plain.","operationId":"open_short_link_v1_c__token__get","parameters":[{"required":true,"schema":{"type":"string","maxLength":32,"title":"Token"},"name":"token","in":"path"}],"responses":{"302":{"description":"To the Binance authorize page."},"404":{"description":"Unknown or expired link (plain text).","content":{"text/plain":{}}},"429":{"description":"More than 30 opens per minute from one IP (plain text).","content":{"text/plain":{}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Connect"]}},"/v1/agent/login":{"post":{"summary":"Start an agent login","description":"Start an agent login. Show the user ONLY verification_uri_complete (or verification_uri + user_code);\nkeep device_code to yourself and poll POST /v1/agent/token with it every `interval` seconds until it\nanswers 200 (or 403 / 410). The login expires after `expires_in` seconds. Ask for the scopes you need with\n`scopes` (e.g. [\"backtest\"] to run engine backtests only, [\"bots\"] to run bots on paper, [\"bots\", \"trade\"] for live trading): the user sees them\npre-ticked and decides; check the scopes the key actually got in the token answer.","operationId":"agent_login_v1_agent_login_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentLoginBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Agent login"]}},"/v1/agent/token":{"post":{"summary":"Poll an agent login for the key","description":"Poll an agent login. 428 authorization_pending (poll again after `interval`), 429 slow_down (polling too\nfast), 403 access_denied (the user denied), 410 expired_token (unknown, expired or already used). Approved:\n200 with the new key, minted now and returned ONCE - save the body to a file (curl -o), never print it.\nThe mint can still answer 403 plan_required or 409 key_limit_reached (5 active keys); start again then.","operationId":"agent_token_v1_agent_token_post","parameters":[{"description":"env: text/plain credentials file","required":false,"schema":{"type":"string","enum":["json","env"],"title":"Format","description":"env: text/plain credentials file","default":"json"},"name":"format","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentTokenBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Agent login"]}},"/v1/agent/claim":{"post":{"summary":"Redeem a claim code for the key","description":"Redeem a one-time claim code the user created in the app (OptAlgo → AI agents → claim code): 200 with the\nnew key, minted now and returned ONCE - save the body to a file (curl -o), never print it. 410 expired_token\nfor an unknown, used or expired code; 429 after 5 failed codes from one IP in 10 minutes.","operationId":"agent_claim_v1_agent_claim_post","parameters":[{"description":"env: text/plain credentials file","required":false,"schema":{"type":"string","enum":["json","env"],"title":"Format","description":"env: text/plain credentials file","default":"json"},"name":"format","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AgentClaimBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Agent login"]}},"/v1/backtests/limits":{"get":{"summary":"V1 Limits","description":"What this account may run on the engine: {plan, enabled, limits, active_jobs, quota: {period, units, used,\nremaining, resets_at, ...}, strike_count: {used, max}} + next. strike_count: strikes of forbidden strategy code;\nat max, API access is suspended.","operationId":"v1_limits_v1_backtests_limits_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["Backtests"],"security":[{"ApiKeyAuth":[]}]}},"/v1/backtests/capabilities":{"get":{"summary":"V1 Capabilities","description":"How to write a strategy: the strategy SDK and primitives, the intents, the RunSpec schema, fee profiles,\nhonesty counters. Read it before writing strategy.py. No strategies are listed: you run your own code.","operationId":"v1_capabilities_v1_backtests_capabilities_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["Backtests"],"security":[{"ApiKeyAuth":[]}]}},"/v1/markets":{"get":{"summary":"V1 Markets","description":"Every market a backtest can use: {data: {exchanges, timeframes, symbols: [{exchange, symbol, type,\nshort_allowed, timeframes: [{timeframe, data_start}], data_start, tick_size, step_size, min_qty,\nmin_notional, backtest[, reason]}], updated_at}, next}. Cached 10 minutes. Only these symbols can be\nbacktested (the platform's market data); `backtest: false` says why.","operationId":"v1_markets_v1_markets_get","parameters":[{"required":false,"schema":{"type":"string","maxLength":32,"title":"Exchange"},"name":"exchange","in":"query"},{"description":"FUTURES | SPOT","required":false,"schema":{"type":"string","title":"Type","description":"FUTURES | SPOT"},"name":"type","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Backtests"],"security":[{"ApiKeyAuth":[]}]}},"/v1/backtests/validate":{"post":{"summary":"V1 Validate","description":"Free dry run (no quota, nothing queued): {data: {ok, findings: [{code, severity, field, message, fix,\nvalue?, allowed?, example?, suggestion?, symbol?, line?, rule?, snippet?, data_start?}], estimate: {run_s,\nsymbols, bars_per_leg, ...}, cost: {units, run_s, seconds_per_unit}, quota, limits[, inputs]}, next}. Every\nproblem at once: code check, spec schema, symbols / timeframe / data for the window, limits, quota. inputs: what a\nstrategy declaring legs / references / data reads and from when (references_detail[], data_detail[], hosted).","operationId":"v1_validate_v1_backtests_validate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Backtests"],"security":[{"ApiKeyAuth":[]}]}},"/v1/backtests/check":{"post":{"summary":"V1 Check","description":"Static check of strategy source (nothing runs): {ok, findings: [{line, col, rule, message}], legs, references,\ndata} + next (legs / references / data: what the source declares, 1 / [] / [] when nothing). POST\n/v1/backtests/validate checks the code AND the spec, with a fix per finding.","operationId":"v1_check_v1_backtests_check_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Backtests"],"security":[{"ApiKeyAuth":[]}]}},"/v1/backtests":{"get":{"summary":"V1 List","description":"This account's backtests, newest first (via: app | api_key; favorite=true: the starred ones only, starring\nis done in the app) + next.","operationId":"v1_list_v1_backtests_get","parameters":[{"required":false,"schema":{"type":"integer","maximum":100.0,"minimum":1.0,"title":"Limit","default":50},"name":"limit","in":"query"},{"required":false,"schema":{"type":"integer","minimum":0.0,"title":"Skip","default":0},"name":"skip","in":"query"},{"required":false,"schema":{"type":"string","title":"Status"},"name":"status","in":"query"},{"required":false,"schema":{"type":"string","title":"Via"},"name":"via","in":"query"},{"required":false,"schema":{"type":"boolean","title":"Favorite"},"name":"favorite","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Backtests"],"security":[{"ApiKeyAuth":[]}]},"post":{"summary":"V1 Submit","description":"Run your strategy code (`source`) as a backtest (spec.research absent) or a research job (spec.research\npresent): 202 {id, status, units, units_estimated, position, eta_s, poll} + next. Without source: 422\nsource_required. Costs quota units by run time (1 unit = up to 10 s; reserved from the estimate, settled to\nthe real run time); over the quota 429 daily_quota / monthly_quota. When busy it queues (fair order: plan\npriority, then round-robin per user). execution.magnify_load defaults to lazy.","operationId":"v1_submit_v1_backtests_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitBody"}}},"required":true},"responses":{"202":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Backtests"],"security":[{"ApiKeyAuth":[]}]}},"/v1/backtests/{backtest_id}":{"get":{"summary":"V1 Get","description":"One backtest: status (queued: position + eta_s), and when done a compact `summary` first (grade, return,\ndrawdown, trades, honesty findings, units charged) then the full result; failed: `fix`. + next.","operationId":"v1_get_v1_backtests__backtest_id__get","parameters":[{"required":true,"schema":{"type":"string","title":"Backtest Id"},"name":"backtest_id","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Backtests"],"security":[{"ApiKeyAuth":[]}]}},"/v1/backtests/{backtest_id}/files/{name}":{"get":{"summary":"V1 File","description":"A file of a finished run (equity.arrow, trades.arrow, result.json, ...).","operationId":"v1_file_v1_backtests__backtest_id__files__name__get","parameters":[{"required":true,"schema":{"type":"string","title":"Backtest Id"},"name":"backtest_id","in":"path"},{"required":true,"schema":{"type":"string","title":"Name"},"name":"name","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Backtests"],"security":[{"ApiKeyAuth":[]}]}},"/v1/backtests/{backtest_id}/source":{"get":{"summary":"V1 Source","description":"Your own strategy code this backtest ran, as a file (text/x-python, attachment \"<name>-<sha8>.py\", the\nsha256 in X-Source-SHA256). 404 no_source when the run had no code of yours.","operationId":"v1_source_v1_backtests__backtest_id__source_get","parameters":[{"required":true,"schema":{"type":"string","title":"Backtest Id"},"name":"backtest_id","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Backtests"],"security":[{"ApiKeyAuth":[]}]}},"/v1/backtests/{backtest_id}/chart":{"get":{"summary":"V1 Chart","description":"A finished backtest's chart data: equity + drawdown downsampled to at most `points` (default 1500, max 5000)\nwith the extremes kept, the trades (2000 most recent), window and symbols. 409 not_ready before it is done.","operationId":"v1_chart_v1_backtests__backtest_id__chart_get","parameters":[{"required":true,"schema":{"type":"string","title":"Backtest Id"},"name":"backtest_id","in":"path"},{"required":false,"schema":{"type":"integer","maximum":100000.0,"minimum":1.0,"title":"Points"},"name":"points","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Backtests"],"security":[{"ApiKeyAuth":[]}]}},"/v1/backtests/{backtest_id}/candles":{"get":{"summary":"V1 Candles","description":"One symbol of a finished backtest: OHLCV in the window (`from` / `to` epoch s) at the backtest timeframe,\naggregated to a coarser bucket past `max_points` (default 2000), the strategy's plot series on the same buckets,\nand the trades + fills in the range. 409 not_ready before it is done.","operationId":"v1_candles_v1_backtests__backtest_id__candles_get","parameters":[{"required":true,"schema":{"type":"string","title":"Backtest Id"},"name":"backtest_id","in":"path"},{"description":"one of the backtest's symbols (default: the first)","required":false,"schema":{"type":"string","maxLength":64,"title":"Symbol","description":"one of the backtest's symbols (default: the first)"},"name":"symbol","in":"query"},{"description":"bucket: a multiple of the run's timeframe (15m, 1h, 4h, 1d, 1w)","required":false,"schema":{"type":"string","maxLength":8,"title":"Tf","description":"bucket: a multiple of the run's timeframe (15m, 1h, 4h, 1d, 1w)"},"name":"tf","in":"query"},{"description":"epoch seconds, clamped to the backtest window","required":false,"schema":{"type":"integer","minimum":0.0,"title":"From","description":"epoch seconds, clamped to the backtest window"},"name":"from","in":"query"},{"description":"epoch seconds, clamped to the backtest window","required":false,"schema":{"type":"integer","minimum":0.0,"title":"To","description":"epoch seconds, clamped to the backtest window"},"name":"to","in":"query"},{"description":"at most this many candles (default 2000, 50-5000)","required":false,"schema":{"type":"integer","maximum":100000.0,"minimum":1.0,"title":"Max Points","description":"at most this many candles (default 2000, 50-5000)"},"name":"max_points","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Backtests"],"security":[{"ApiKeyAuth":[]}]}},"/v1/backtests/{backtest_id}/trades.csv":{"get":{"summary":"V1 Trades Csv","description":"Every trade of a finished backtest as CSV: symbol, side, entry_time, entry_price, exit_time, exit_price, size,\npnl, fees, reason (ISO 8601 UTC; pnl net of fees and funding).","operationId":"v1_trades_csv_v1_backtests__backtest_id__trades_csv_get","parameters":[{"required":true,"schema":{"type":"string","title":"Backtest Id"},"name":"backtest_id","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Backtests"],"security":[{"ApiKeyAuth":[]}]}},"/v1/strategies/limits":{"get":{"summary":"Strategies Limits","description":"What this plan allows: hosted_strategy_limit, hosted_symbol_limit, hosted_cpu_s_per_day, slots used / left,\nlive allowed, paper trades left this month, the live eligibility rule, whether hosting is available.","operationId":"strategies_limits_v1_strategies_limits_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["Hosted strategies"],"security":[{"ApiKeyAuth":[]}]}},"/v1/strategies":{"get":{"summary":"List Strategies","description":"The account's hosted strategies, newest first (deleted ones left out).","operationId":"list_strategies_v1_strategies_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["Hosted strategies"],"security":[{"ApiKeyAuth":[]}]},"post":{"summary":"Deploy Strategy","description":"Deploy a backtested strategy on paper (see the module docstring). dry_run=true runs the whole gate and\ncreates nothing.","operationId":"deploy_strategy_v1_strategies_post","parameters":[{"required":false,"schema":{"type":"boolean","title":"Dry Run","default":false},"name":"dry_run","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeployBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Hosted strategies"],"security":[{"ApiKeyAuth":[]}]}},"/v1/strategies/{hosted_id}":{"get":{"summary":"Get Strategy","operationId":"get_strategy_v1_strategies__hosted_id__get","parameters":[{"required":true,"schema":{"type":"string","title":"Hosted Id"},"name":"hosted_id","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Hosted strategies"],"security":[{"ApiKeyAuth":[]}]},"delete":{"summary":"Delete Strategy","description":"Flat: removed now. Open positions: winding_down (only exits until flat), or close=true to close them now\n(a LIVE strategy's close needs the trade scope).","operationId":"delete_strategy_v1_strategies__hosted_id__delete","parameters":[{"required":true,"schema":{"type":"string","title":"Hosted Id"},"name":"hosted_id","in":"path"},{"required":false,"schema":{"type":"boolean","title":"Close","default":false},"name":"close","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Hosted strategies"],"security":[{"ApiKeyAuth":[]}]},"patch":{"summary":"Patch Strategy","description":"name / allocated_amount / on_leg_failure (unwind | keep, multi-leg strategies), on paper only (409\nlive_strategy otherwise; 409 while a position is open for an allocation change).","operationId":"patch_strategy_v1_strategies__hosted_id__patch","parameters":[{"required":true,"schema":{"type":"string","title":"Hosted Id"},"name":"hosted_id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PatchBody"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Hosted strategies"],"security":[{"ApiKeyAuth":[]}]}},"/v1/strategies/{hosted_id}/decisions":{"get":{"summary":"Strategy Decisions","description":"Newest first: per bar a record row (symbol \"*\": decided / skipped / error / paused with a plain message),\na row per action (outcome sent / refused / skipped / duplicate + code + message; a sent one carries its\nsignal_status from the ledger, the transaction_id to follow and the trade_id when resolvable) and a row per\nquiet symbol. Kept 90 days.","operationId":"strategy_decisions_v1_strategies__hosted_id__decisions_get","parameters":[{"required":true,"schema":{"type":"string","title":"Hosted Id"},"name":"hosted_id","in":"path"},{"required":false,"schema":{"type":"string","maxLength":50,"title":"Symbol"},"name":"symbol","in":"query"},{"description":"bar_t (epoch s): older rows only","required":false,"schema":{"type":"integer","title":"Before","description":"bar_t (epoch s): older rows only"},"name":"before","in":"query"},{"required":false,"schema":{"type":"integer","maximum":200.0,"minimum":1.0,"title":"Limit","default":50},"name":"limit","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Hosted strategies"],"security":[{"ApiKeyAuth":[]}]}},"/v1/strategies/{hosted_id}/logs":{"get":{"summary":"Strategy Logs","description":"The bot's log feed as My Bots shows it: signals received, orders, refusals with their reason, status lines.","operationId":"strategy_logs_v1_strategies__hosted_id__logs_get","parameters":[{"required":true,"schema":{"type":"string","title":"Hosted Id"},"name":"hosted_id","in":"path"},{"required":false,"schema":{"type":"integer","maximum":200.0,"minimum":1.0,"title":"Limit","default":50},"name":"limit","in":"query"},{"required":false,"schema":{"type":"number","title":"Before"},"name":"before","in":"query"},{"required":false,"schema":{"type":"string","enum":["INFO","WARNING","ERROR"],"title":"Level"},"name":"level","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Hosted strategies"],"security":[{"ApiKeyAuth":[]}]}},"/v1/strategies/{hosted_id}/source":{"get":{"summary":"Strategy Source","description":"Your own strategy code this hosted strategy runs, as a .py file (match it with code.source_sha256).","operationId":"strategy_source_v1_strategies__hosted_id__source_get","parameters":[{"required":true,"schema":{"type":"string","title":"Hosted Id"},"name":"hosted_id","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Hosted strategies"],"security":[{"ApiKeyAuth":[]}]}},"/v1/strategies/{hosted_id}/pause":{"post":{"summary":"Pause Strategy","operationId":"pause_strategy_v1_strategies__hosted_id__pause_post","parameters":[{"required":true,"schema":{"type":"string","title":"Hosted Id"},"name":"hosted_id","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Hosted strategies"],"security":[{"ApiKeyAuth":[]}]}},"/v1/strategies/{hosted_id}/resume":{"post":{"summary":"Resume Strategy","operationId":"resume_strategy_v1_strategies__hosted_id__resume_post","parameters":[{"required":true,"schema":{"type":"string","title":"Hosted Id"},"name":"hosted_id","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Hosted strategies"],"security":[{"ApiKeyAuth":[]}]}},"/v1/strategies/{hosted_id}/request-live":{"post":{"summary":"Request Live","description":"Ask the account owner to take the strategy live. Plus / Pro and the trade scope; it NEVER flips anything:\nthe owner confirms in the app (My Bots). Never gated by the recommended paper period (owner 2026-10-10): the\nanswer's live_eligibility carries the progress and recommended_ready; before it, the owner may still confirm\nearly by accepting the risk in the app. Never tell the user it is live until GET says status live.","operationId":"request_live_v1_strategies__hosted_id__request_live_post","parameters":[{"required":true,"schema":{"type":"string","title":"Hosted Id"},"name":"hosted_id","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Hosted strategies"],"security":[{"ApiKeyAuth":[]}]}},"/v1/strategies/{hosted_id}/compare":{"get":{"summary":"List Compares","operationId":"list_compares_v1_strategies__hosted_id__compare_get","parameters":[{"required":true,"schema":{"type":"string","title":"Hosted Id"},"name":"hosted_id","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Hosted strategies"],"security":[{"ApiKeyAuth":[]}]},"post":{"summary":"Compare Strategy","description":"Compare the strategy's real trades (paper or live) with a backtest of the same code, params and symbols over\nthe period it has been running. dry_run=true: the estimate (units, run_s, window, mode), free. Else 202: the\nbacktest is queued and charged by run time like any backtest (the same window end again is reused, free);\npoll GET .../compare/{id} until status is done.","operationId":"compare_strategy_v1_strategies__hosted_id__compare_post","parameters":[{"required":true,"schema":{"type":"string","title":"Hosted Id"},"name":"hosted_id","in":"path"},{"required":false,"schema":{"type":"boolean","title":"Dry Run","default":false},"name":"dry_run","in":"query"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/routes__v1_strategies__CompareBody"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Hosted strategies"],"security":[{"ApiKeyAuth":[]}]}},"/v1/strategies/{hosted_id}/compare/{compare_id}":{"get":{"summary":"Get Compare","description":"status queued / running (position, eta_s) until the backtest is done, then the report: summary\n{decisions_matched, decisions_total, match_rate, missed, extra, pnl_actual, pnl_backtest, pnl_diff,\navg_entry_slippage_bps, avg_exit_slippage_bps, fees_actual, fees_backtest, ...}, mismatches (each with bar_t\nand reason), matched (paged: offset / limit), histogram, open_positions; actual=true adds actual_trades.","operationId":"get_compare_v1_strategies__hosted_id__compare__compare_id__get","parameters":[{"required":true,"schema":{"type":"string","title":"Hosted Id"},"name":"hosted_id","in":"path"},{"required":true,"schema":{"type":"string","title":"Compare Id"},"name":"compare_id","in":"path"},{"required":false,"schema":{"type":"integer","minimum":0.0,"title":"Offset","default":0},"name":"offset","in":"query"},{"required":false,"schema":{"type":"integer","maximum":200.0,"minimum":1.0,"title":"Limit","default":50},"name":"limit","in":"query"},{"required":false,"schema":{"type":"boolean","title":"Actual","default":false},"name":"actual","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Hosted strategies"],"security":[{"ApiKeyAuth":[]}]}}},"components":{"schemas":{"AgentClaimBody":{"properties":{"claim_code":{"type":"string","title":"Claim Code","description":"OPTC-XXXX-XXXX-XXXX-XXXX from the app (case and dashes optional)."}},"additionalProperties":false,"type":"object","required":["claim_code"],"title":"AgentClaimBody"},"AgentLoginBody":{"properties":{"client_name":{"type":"string","maxLength":60,"title":"Client Name","description":"Shown to the user when they approve, e.g. \"Claude Code on MacBook\"."},"scopes":{"items":{"type":"string"},"type":"array","maxItems":10,"title":"Scopes","description":"The scopes the agent asks for (read, backtest, bots, trade; read is always added): pre-ticked on the approval page. A suggestion only - the user chooses the scopes the key gets. backtest = run engine backtests only; bots = manage bots and trade them on paper; trade = live trading. Unknown scope: 422 invalid_scope."}},"additionalProperties":false,"type":"object","title":"AgentLoginBody"},"AgentTokenBody":{"properties":{"device_code":{"type":"string","title":"Device Code","description":"The device_code from POST /v1/agent/login."}},"additionalProperties":false,"type":"object","required":["device_code"],"title":"AgentTokenBody"},"CheckBody":{"properties":{"source":{"type":"string","title":"Source"}},"additionalProperties":false,"type":"object","required":["source"],"title":"CheckBody"},"ChildBody":{"properties":{"weight":{"type":"number","exclusiveMinimum":0.0,"title":"Weight"},"paused":{"type":"boolean","title":"Paused"}},"additionalProperties":false,"type":"object","title":"ChildBody"},"CloseBody":{"properties":{"ticker":{"type":"string","maxLength":50,"title":"Ticker"},"close_ratio":{"type":"number","maximum":1.0,"exclusiveMinimum":0.0,"title":"Close Ratio"}},"additionalProperties":false,"type":"object","title":"CloseBody"},"ConvertBody":{"properties":{"max_positions":{"type":"integer","minimum":1.0,"title":"Max Positions"}},"additionalProperties":false,"type":"object","required":["max_positions"],"title":"ConvertBody"},"CreateBotBody":{"properties":{"type":{"type":"string","enum":["single","auto","multi"],"title":"Type"},"name":{"type":"string","maxLength":100,"title":"Name"},"exchange_id":{"type":"string","title":"Exchange Id"},"account_type":{"type":"string","enum":["SPOT","FUTURES","MARGIN"],"title":"Account Type"},"symbol":{"type":"string","title":"Symbol"},"is_paper":{"type":"boolean","title":"Is Paper","default":true},"allocated_amount":{"type":"number","minimum":0.0,"title":"Allocated Amount"},"max_positions":{"type":"integer","minimum":1.0,"title":"Max Positions"}},"additionalProperties":false,"type":"object","required":["type"],"title":"CreateBotBody"},"DeployBody":{"properties":{"backtest_id":{"type":"string","maxLength":64,"minLength":1,"title":"Backtest Id"},"name":{"type":"string","maxLength":100,"title":"Name"},"symbols":{"items":{"type":"string"},"type":"array","maxItems":50,"title":"Symbols"},"params":{"type":"object","title":"Params"},"timeframe":{"type":"string","maxLength":10,"title":"Timeframe"},"exchange":{"type":"string","maxLength":20,"title":"Exchange"},"leverage":{"type":"number","maximum":125.0,"minimum":1.0,"title":"Leverage"},"source":{"type":"string","maxLength":65536,"title":"Source"},"source_sha256":{"type":"string","pattern":"^[0-9a-f]{64}$","title":"Source Sha256"},"allocated_amount":{"type":"number","exclusiveMinimum":0.0,"title":"Allocated Amount"},"on_error":{"type":"string","enum":["hold","close"],"title":"On Error"},"on_leg_failure":{"type":"string","enum":["unwind","keep"],"title":"On Leg Failure"}},"additionalProperties":false,"type":"object","required":["backtest_id"],"title":"DeployBody"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"PatchBody":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name"},"allocated_amount":{"type":"number","exclusiveMinimum":0.0,"title":"Allocated Amount"},"on_leg_failure":{"type":"string","enum":["unwind","keep"],"title":"On Leg Failure"}},"additionalProperties":false,"type":"object","title":"PatchBody"},"SignalBody":{"properties":{"position_side":{"type":"string","title":"Position Side"},"ticker":{"type":"string","maxLength":50,"title":"Ticker"},"exchange":{"type":"string","maxLength":50,"title":"Exchange"},"action":{"type":"string","enum":["BUY","SELL"],"title":"Action"},"trade_id":{"type":"string","maxLength":36,"title":"Trade Id"},"entry_price":{"type":"number","title":"Entry Price"},"stop_loss_price":{"type":"number","title":"Stop Loss Price"},"take_profit_price":{"type":"number","title":"Take Profit Price"},"stop_loss_pct":{"type":"number","exclusiveMaximum":100.0,"exclusiveMinimum":0.0,"title":"Stop Loss Pct"},"take_profit_pct":{"type":"number","maximum":1000.0,"exclusiveMinimum":0.0,"title":"Take Profit Pct"},"entry_order_type":{"type":"string","title":"Entry Order Type"},"entry_order_time_in_force_type":{"type":"string","title":"Entry Order Time In Force Type"},"exit_order_type":{"type":"string","title":"Exit Order Type"},"exit_order_time_in_force_type":{"type":"string","title":"Exit Order Time In Force Type"},"chase_order_type":{"type":"string","title":"Chase Order Type"},"chase_exit_order_type":{"type":"string","title":"Chase Exit Order Type"},"margin_mode":{"type":"string","enum":["isolated","cross"],"title":"Margin Mode"},"multi_asset_mode":{"type":"boolean","title":"Multi Asset Mode"},"leverage":{"type":"integer","maximum":125.0,"minimum":1.0,"title":"Leverage"},"tradable_ratio":{"type":"number","maximum":1.0,"exclusiveMinimum":0.0,"title":"Tradable Ratio"},"close_ratio":{"type":"number","maximum":1.0,"exclusiveMinimum":0.0,"title":"Close Ratio"},"callback_rate":{"type":"number","title":"Callback Rate"},"is_trailing_stop_enabled":{"type":"boolean","title":"Is Trailing Stop Enabled"},"cancel_stop_loss":{"type":"boolean","title":"Cancel Stop Loss"},"cancel_take_profit":{"type":"boolean","title":"Cancel Take Profit"},"safety_check":{"type":"boolean","title":"Safety Check"},"safety_check_interval_minutes":{"type":"integer","title":"Safety Check Interval Minutes"},"in_position":{"type":"boolean","title":"In Position"},"reason":{"type":"string","maxLength":200,"title":"Reason"}},"additionalProperties":false,"type":"object","required":["position_side"],"title":"SignalBody","description":"The TradingView webhook fields an agent may send (freedx-signal-listener models/request_models.py).\nstrategy_key, exchange of a configured bot and transaction_id are filled in by the API."},"SubmitBody":{"properties":{"spec":{"type":"object","title":"Spec"},"source":{"type":"string","title":"Source"}},"additionalProperties":false,"type":"object","required":["spec"],"title":"SubmitBody"},"TicketBatch":{"properties":{"items":{"items":{"$ref":"#/components/schemas/TicketItem"},"type":"array","title":"Items"}},"additionalProperties":false,"type":"object","required":["items"],"title":"TicketBatch"},"TicketContext":{"properties":{"bot_id":{"type":"string","maxLength":100,"title":"Bot Id"},"transaction_id":{"type":"string","maxLength":100,"title":"Transaction Id"},"trade_id":{"type":"string","maxLength":100,"title":"Trade Id"},"backtest_id":{"type":"string","maxLength":100,"title":"Backtest Id"},"hosted_id":{"type":"string","maxLength":100,"title":"Hosted Id"},"request_id":{"type":"string","maxLength":100,"title":"Request Id"}},"additionalProperties":false,"type":"object","title":"TicketContext"},"TicketItem":{"properties":{"kind":{"type":"string","enum":["bug","feature","question"],"title":"Kind"},"title":{"type":"string","title":"Title"},"body":{"type":"string","title":"Body"},"context":{"$ref":"#/components/schemas/TicketContext"}},"additionalProperties":false,"type":"object","required":["kind","title","body"],"title":"TicketItem"},"UpdateBotBody":{"properties":{"name":{"type":"string","maxLength":100,"minLength":1,"title":"Name"},"allocated_amount":{"type":"number","minimum":0.0,"title":"Allocated Amount"},"is_paper":{"type":"boolean","title":"Is Paper"},"max_positions":{"type":"integer","minimum":1.0,"title":"Max Positions"}},"additionalProperties":false,"type":"object","title":"UpdateBotBody"},"ValidateBody":{"properties":{"spec":{"type":"object","title":"Spec"},"source":{"type":"string","title":"Source"}},"additionalProperties":false,"type":"object","required":["spec"],"title":"ValidateBody"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"routes__v1_strategies__CompareBody":{"properties":{"mode":{"type":"string","enum":["paper","live"],"title":"Mode"}},"additionalProperties":false,"type":"object","title":"CompareBody"}},"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"X-API-Key","description":"An OptAlgo API key (oa_live_...). Scopes: read (always) = read the account, bots and results; backtest = run engine backtests (POST /v1/backtests, /v1/backtests/check) and nothing else; bots = manage bots and trade them on paper; trade = live trading: signals and closes on live bots, switching to live, creating live bots, webhook keys. bots and trade also allow backtests."}}}}