{
  "openapi": "3.0.3",
  "info": {
    "title": "Thor Trade Copier API",
    "description": "Programmatic access to Thor Trade Copier. The REST API (X-API-Key and X-API-Secret headers) manages connections, accounts, copy links, positions and risk settings. Orders are sent through the order webhook, POST /hooks/tv/{token}, which takes JSON from TradingView, a bot, a script or an AI agent. Guide: https://thortradecopier.com/developers",
    "version": "1.0.0",
    "contact": {
      "name": "Thor Support",
      "url": "https://thortradecopier.com",
      "email": "support@thortradecopier.com"
    }
  },
  "servers": [
    {
      "url": "https://app.thortradecopier.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": [],
      "ApiSecretAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Your Thor API key"
      },
      "ApiSecretAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Secret",
        "description": "Your Thor API secret"
      }
    },
    "parameters": {
      "XCustomerId": {
        "name": "X-Customer-Id",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "Target customer ID for master API keys. Required for most endpoints when using a master key. Omit to use aggregated mode on supported endpoints (e.g., GET /positions)."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Error code"
          },
          "message": {
            "type": "string",
            "description": "Human-readable error message"
          }
        },
        "required": [
          "error",
          "message"
        ]
      },
      "Connection": {
        "type": "object",
        "properties": {
          "userId": {
            "type": "string",
            "description": "Unique connection ID",
            "example": "user_1"
          },
          "vendorName": {
            "type": "string",
            "description": "Vendor name",
            "example": "Rithmic"
          },
          "vendorIcon": {
            "type": "string",
            "description": "URL to vendor icon",
            "example": "/images/vendorreduced/rithmic.png"
          },
          "accountId": {
            "type": "string",
            "description": "Account ID / Username - the login this connection was created with. This is also the value to pass as `phoenixSource` / `convertedSource` when creating an account configuration for one of the accounts listed in this connection's `availableAccounts`.",
            "example": "trader@example.com"
          },
          "customName": {
            "type": "string",
            "description": "Custom display name",
            "example": "My TopStep Account"
          },
          "server": {
            "type": "string",
            "description": "Server address",
            "example": "dd.rithmic.com"
          },
          "selectedConnection": {
            "type": "string",
            "description": "Demo or Live",
            "example": "Demo"
          },
          "enabled": {
            "type": "boolean",
            "description": "Whether connection is active",
            "example": true
          },
          "status": {
            "type": "string",
            "description": "Current connection status",
            "example": "Connected"
          },
          "groupName": {
            "type": "string",
            "description": "Group name for organization",
            "example": "Rithmic"
          },
          "isCrossTradingActivated": {
            "type": "boolean",
            "description": "Cross-trading enabled",
            "example": false
          },
          "isMarketDataActivated": {
            "type": "boolean",
            "description": "Market data enabled",
            "example": true
          },
          "availableAccounts": {
            "type": "array",
            "description": "Trading accounts available under this connection. Use `AccountId` to match positions, and as the `phoenixId` / `convertedId` when creating an account configuration. Empty until the connection finishes connecting (roughly 5-20s after save-connections).",
            "items": {
              "type": "object",
              "properties": {
                "AccountId": {
                  "type": "string",
                  "description": "Broker account number (matches AccountId in positions)",
                  "example": "40362582"
                },
                "AccountName": {
                  "type": "string",
                  "description": "Display name for the account",
                  "example": "APEX-40362582"
                }
              }
            }
          }
        }
      },
      "ConnectionCreate": {
        "type": "object",
        "required": [
          "vendorName",
          "accountId"
        ],
        "description": "⚠️ **IMPORTANT:** After creating/updating/deleting connections, you MUST call `POST /api/v1/config/save-connections` to apply your changes and trigger the connection process.\n\nOAuth-based vendors (Tradovate, cTrader) are NOT supported via API. Use the Thor dashboard for OAuth connections.",
        "properties": {
          "vendorName": {
            "type": "string",
            "description": "Vendor name (see /api/v1/vendors for list). OAuth vendors (Tradovate, cTrader) are NOT supported."
          },
          "accountId": {
            "type": "string",
            "description": "Account ID / Username / Email for the vendor. TradeLocker: the login email. iFX: the full account number including the iFXL prefix and its zero padding (e.g. iFXL000012345678); the bare number is rejected by iFX."
          },
          "password": {
            "type": "string",
            "description": "Account password (required for credential-based vendors)"
          },
          "server": {
            "type": "string",
            "description": "Server address (required for Rithmic-based vendors and prop firms). TradeLocker: the TradeLocker server name from the prop firm's credentials email (e.g. \"Plutus\"); it is stored with its environment as \"demo|Plutus\" or \"live|Plutus\". iFX: not needed, its fixed server is set automatically."
          },
          "selectedConnection": {
            "type": "string",
            "enum": [
              "Demo",
              "Live",
              "Real"
            ],
            "default": "Demo",
            "description": "Connection type: Demo, Live, or Real. TradeLocker accepts Demo or Live only. iFX takes none (demo and funded accounts are told apart by iFX itself)."
          },
          "customName": {
            "type": "string",
            "description": "Custom display name for this connection"
          },
          "groupName": {
            "type": "string",
            "description": "Group name for organization (defaults to vendorName)"
          },
          "enabled": {
            "type": "boolean",
            "default": true,
            "description": "Enable connection on creation"
          },
          "isCrossTradingActivated": {
            "type": "boolean",
            "default": false,
            "description": "Enable cross-trading (for Rithmic vendors)"
          },
          "isMarketDataActivated": {
            "type": "boolean",
            "default": false,
            "description": "Enable market data feed"
          }
        },
        "examples": {
          "Rithmic Paper Trading": {
            "summary": "Rithmic Paper Trading account",
            "value": {
              "vendorName": "Rithmic",
              "accountId": "trader@example.com",
              "password": "mySecurePassword123",
              "server": "Rithmic Paper Trading",
              "customName": "My Personal Paper Account",
              "groupName": "Rithmic",
              "enabled": true,
              "isCrossTradingActivated": false,
              "isMarketDataActivated": true
            }
          },
          "Apex Trader Funding": {
            "summary": "Apex Trader Funding on Rithmic",
            "value": {
              "vendorName": "Apex Trader Funding",
              "accountId": "myapexuser@email.com",
              "password": "apexPassword123",
              "server": "Apex Chicago Area Summary",
              "customName": "A cool propfirm",
              "groupName": "Rithmic",
              "enabled": true,
              "isCrossTradingActivated": false,
              "isMarketDataActivated": true
            }
          },
          "dxFeed (MyFundedFutures)": {
            "summary": "dxFeed connection for MyFundedFutures - no server needed",
            "value": {
              "vendorName": "MyFundedFutures",
              "accountId": "mffu_username",
              "password": "mffuPassword123",
              "selectedConnection": "Demo",
              "customName": "MFFU Demo Account",
              "groupName": "dxFeed",
              "enabled": true
            }
          },
          "dxFeed (Phoenix Trader Funding)": {
            "summary": "dxFeed connection for Phoenix Trader Funding - no server needed",
            "value": {
              "vendorName": "dxFeed",
              "accountId": "phoenix_user@email.com",
              "password": "phoenixPassword123",
              "selectedConnection": "Real",
              "customName": "Phoenix Live Account",
              "groupName": "Phoenix Trader Funding",
              "enabled": true
            }
          },
          "TopStep (Rithmic)": {
            "summary": "TopStep on Rithmic",
            "value": {
              "vendorName": "TopStep",
              "accountId": "topstepuser@email.com",
              "password": "topstepPassword123",
              "server": "TopstepTrader Chicago Area",
              "customName": "TopStep Combine",
              "groupName": "Rithmic",
              "enabled": true,
              "isCrossTradingActivated": false,
              "isMarketDataActivated": true
            }
          },
          "MetaTrader 5": {
            "summary": "MetaTrader 5 account - requires broker server name",
            "value": {
              "vendorName": "mt5",
              "accountId": "5048768557",
              "password": "myMT5password",
              "server": "MetaQuotes-Demo",
              "customName": "My MT5 Demo Account",
              "groupName": "MetaTrader 5",
              "enabled": true
            }
          },
          "MetaTrader 4": {
            "summary": "MetaTrader 4 account - requires broker server name",
            "value": {
              "vendorName": "mt4",
              "accountId": "500476959",
              "password": "myMT4password",
              "server": "FreshForex-Demo",
              "customName": "My MT4 Demo Account",
              "groupName": "MetaTrader 4",
              "enabled": true
            }
          }
        }
      },
      "ConnectionUpdate": {
        "type": "object",
        "description": "⚠️ **IMPORTANT:** After updating connections, you MUST call `POST /api/v1/config/save-connections` to apply your changes.",
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "Enable or disable the connection"
          },
          "customName": {
            "type": "string",
            "description": "Custom display name"
          },
          "groupName": {
            "type": "string",
            "description": "Group name"
          },
          "password": {
            "type": "string",
            "description": "Update password"
          },
          "server": {
            "type": "string",
            "description": "Update server address. TradeLocker: send the server name alone; it keeps the connection's environment unless selectedConnection changes it. iFX: ignored, its server is fixed."
          },
          "selectedConnection": {
            "type": "string",
            "enum": [
              "Demo",
              "Live"
            ],
            "description": "Demo or Live connection. On TradeLocker this moves the stored server to that environment."
          },
          "isCrossTradingActivated": {
            "type": "boolean",
            "description": "Enable/disable cross-trading"
          },
          "isMarketDataActivated": {
            "type": "boolean",
            "description": "Enable/disable market data"
          }
        }
      },
      "Account": {
        "type": "object",
        "description": "One leader->follower copy configuration, exactly as returned by GET /api/v1/accounts.",
        "properties": {
          "clientId": {
            "type": "string",
            "description": "Unique configuration ID. Use it as the {id} path parameter for PUT and DELETE."
          },
          "phoenixId": {
            "type": "string",
            "description": "Leader account ID - the account copied FROM"
          },
          "phoenixName": {
            "type": "string",
            "description": "Leader display name, resolved from the discovered accounts. Falls back to the account ID when the broker reports no name."
          },
          "convertedId": {
            "type": "string",
            "description": "Follower account ID - the account copied TO"
          },
          "convertedName": {
            "type": "string",
            "description": "Follower display name, resolved the same way as phoenixName"
          },
          "phoenixIdSource": {
            "type": "string",
            "description": "The connection that owns the leader account, i.e. that connection's `accountId` in GET /api/v1/connections. Set automatically; the copy engine uses it to route orders to the right connection and to detect CFD-style accounts (cTrader, DXtrade, MetaTrader). An empty value means the account could not be matched to a connection."
          },
          "convertedIdSource": {
            "type": "string",
            "description": "The connection that owns the follower account, resolved the same way as phoenixIdSource"
          },
          "enabled": {
            "type": "boolean",
            "description": "Whether this configuration is active and copying"
          },
          "multiplier": {
            "type": "number",
            "description": "Position multiplier applied to the follower size",
            "example": 1
          },
          "stopLoss": {
            "type": "number",
            "description": "Daily stop loss limit in dollars (negative). The value -88390 is the \"no limit\" sentinel, not a real -$88,390 stop.",
            "example": -500
          },
          "takeProfit": {
            "type": "number",
            "description": "Daily take profit goal in dollars (positive). The value 88390 is the \"no limit\" sentinel.",
            "example": 500
          },
          "isReversed": {
            "type": "boolean",
            "description": "Reverse trading mode - the follower buys when the leader sells",
            "example": false
          },
          "isAutoLiquidateLeader": {
            "type": "boolean",
            "description": "Auto-liquidate the leader account when the stop loss or take profit is hit",
            "example": false
          },
          "isLiquidation": {
            "type": "boolean",
            "description": "True while this configuration is in a liquidation state",
            "example": false
          }
        }
      },
      "AccountCreate": {
        "type": "object",
        "required": [
          "phoenixId",
          "convertedId"
        ],
        "description": "⚠️ **IMPORTANT:** After creating/updating/deleting account configurations, you MUST call `POST /api/v1/config/save-accounts` to apply your changes.",
        "properties": {
          "phoenixId": {
            "type": "string",
            "description": "Leader account ID (the account to copy FROM) - get this from GET /api/v1/connections"
          },
          "convertedId": {
            "type": "string",
            "description": "Follower account ID (the account to copy TO) - get this from GET /api/v1/connections"
          },
          "enabled": {
            "type": "boolean",
            "default": true,
            "description": "Enable configuration on creation (default: true)"
          },
          "multiplier": {
            "type": "number",
            "default": 1,
            "description": "Position multiplier (default: 1). Written verbatim, with NO unit conversion. Copying between a futures account and a CFD account (cTrader, MetaTrader, DXtrade) crosses two different sizing models, so you must pre-scale this value yourself: the dashboard multiplies by 1e-7 (futures leader -> CFD follower) or 1e7 (CFD leader -> futures follower) for FX, 0.01 / 100 for indices, and 0.0001 / 10000 for gold. Sending 1 across such a pair will size the follower wrongly by orders of magnitude."
          },
          "stopLoss": {
            "type": "number",
            "default": -88390,
            "description": "Daily stop loss limit in dollars. MUST BE NEGATIVE: a positive value is REJECTED with 400 invalid_stop_loss, it is never auto-corrected. OMITTING THIS MEANS NO DAILY STOP LOSS: the default -88390 is the \"unset\" sentinel the dashboard writes, not a real -88390 limit. Pass an explicit negative value (e.g. -500) to stop at a loss. Note 88390 (positive) is the TAKE PROFIT sentinel, not the stop loss one."
          },
          "takeProfit": {
            "type": "number",
            "default": 88390,
            "description": "Daily take profit goal in dollars, must be positive. OMITTING THIS MEANS NO DAILY TAKE PROFIT: the default 88390 is the \"unset\" sentinel the dashboard writes. Pass an explicit positive value (e.g. 500) to set a real goal."
          },
          "copyMode": {
            "type": "string",
            "enum": [
              "fillsonly",
              "allorders"
            ],
            "default": "fillsonly",
            "description": "How orders are copied. Optional; defaults to \"fillsonly\" (recommended: copies once the leader order is filled). \"allorders\" also mirrors the leader's working orders and requires All Orders to be unlocked for this customer (contact support), otherwise the request is rejected with 403 allorders_locked. NinjaTrader followers do not support order modification, so \"allorders\" is silently downgraded to \"fillsonly\" for them (same rule as the dashboard) and the response carries a `notice`."
          },
          "phoenixSource": {
            "type": "string",
            "description": "Optional. The accountId of the connection that owns the leader account - i.e. the `accountId` of the entry in GET /api/v1/connections whose `availableAccounts` contains this phoenixId. Normally derived automatically; only required when the same account id exists under more than one connection, which is rejected with 409 ambiguous_account."
          },
          "convertedSource": {
            "type": "string",
            "description": "Optional. The accountId of the connection that owns the follower account, resolved the same way as phoenixSource."
          },
          "isReversed": {
            "type": "boolean",
            "default": false,
            "description": "Reverse trading mode - the follower buys when the leader sells (default: false)"
          },
          "isAutoLiquidateLeader": {
            "type": "boolean",
            "default": false,
            "description": "Auto-liquidate the leader account when the stop loss or take profit is hit (default: false)"
          },
          "isDisableMicroOrders": {
            "type": "boolean",
            "default": false,
            "description": "Disable conversion to micro contracts when the multiplier produces a fractional size (default: false)"
          }
        },
        "example": {
          "phoenixId": "APEX-12345-Leader",
          "convertedId": "APEX-67890-Follower",
          "enabled": true,
          "multiplier": 1,
          "stopLoss": -500,
          "takeProfit": 500,
          "copyMode": "fillsonly"
        }
      },
      "AccountUpdate": {
        "type": "object",
        "description": "Only the five fields below can be updated. Anything else - the leader/follower accounts, their owning connections, copy mode, reverse mode and the symbol map - is fixed at creation: delete the configuration and create a new one to change those.\n\n⚠️ **IMPORTANT:** After updating account configurations, you MUST call `POST /api/v1/config/save-accounts` to apply your changes.",
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "Enable or disable the configuration"
          },
          "multiplier": {
            "type": "number",
            "description": "Position multiplier, stored exactly as sent (no unit conversion - see AccountCreate.multiplier)"
          },
          "stopLoss": {
            "type": "number",
            "description": "Daily stop loss limit in dollars. MUST BE NEGATIVE: a positive value is REJECTED with 400 invalid_stop_loss, it is never auto-corrected. Use -88390 for \"no limit\". Note 88390 (positive) is the TAKE PROFIT sentinel."
          },
          "takeProfit": {
            "type": "number",
            "description": "Daily take profit goal in dollars (positive). Use 88390 for \"no limit\"."
          },
          "isAutoLiquidateLeader": {
            "type": "boolean",
            "description": "Auto-liquidate the leader account when the stop loss or take profit is hit"
          }
        }
      },
      "Position": {
        "type": "object",
        "properties": {
          "accountId": {
            "type": "string",
            "description": "Broker account number"
          },
          "userId": {
            "type": "string",
            "description": "Thor connection ID (e.g. user_1) - maps to connections endpoint"
          },
          "symbol": {
            "type": "string"
          },
          "quantity": {
            "type": "number"
          },
          "side": {
            "type": "string",
            "enum": [
              "long",
              "short"
            ]
          },
          "entryPrice": {
            "type": "number"
          },
          "pnl": {
            "type": "number"
          }
        }
      },
      "LockedAccount": {
        "type": "object",
        "properties": {
          "accountId": {
            "type": "string",
            "description": "Account ID that is locked"
          },
          "unlockTime": {
            "type": "number",
            "description": "Unix timestamp (ms) when the lock expires"
          },
          "lockedAt": {
            "type": "number",
            "description": "Unix timestamp (ms) when the lock was created"
          },
          "reason": {
            "type": "string",
            "description": "Reason for the lock"
          },
          "remainingMinutes": {
            "type": "number",
            "description": "Minutes remaining until unlock"
          }
        }
      },
      "LockAccountsRequest": {
        "type": "object",
        "required": [
          "accountIds",
          "durationMinutes"
        ],
        "properties": {
          "accountIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Array of account IDs to lock"
          },
          "durationMinutes": {
            "type": "number",
            "description": "Duration to lock accounts (in minutes)"
          },
          "reason": {
            "type": "string",
            "description": "Reason for locking (optional)"
          }
        },
        "example": {
          "accountIds": [
            "APEX-12345",
            "APEX-67890"
          ],
          "durationMinutes": 60,
          "reason": "Daily loss limit reached"
        }
      },
      "NewsRestriction": {
        "type": "object",
        "properties": {
          "connectionKey": {
            "type": "string",
            "description": "Unique connection identifier (format: username|||vendor|||server)"
          },
          "enabled": {
            "type": "boolean",
            "description": "Whether news restrictions are enabled for this connection"
          },
          "restrictedEvents": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "List of news events to restrict (e.g., FOMC, NFP, CPI)"
          },
          "timeBeforeMinutes": {
            "type": "number",
            "description": "Minutes before news to pause trading"
          },
          "notifyBeforeMinutes": {
            "type": "number",
            "description": "Minutes before restriction to send notification"
          },
          "includeUSOpen": {
            "type": "boolean",
            "description": "Include US market open in restrictions"
          },
          "includeUKOpen": {
            "type": "boolean",
            "description": "Include UK market open in restrictions"
          },
          "includeAsiaOpen": {
            "type": "boolean",
            "description": "Include Asia market open in restrictions"
          },
          "marketOpenAfterMinutes": {
            "type": "number",
            "description": "Minutes after market open to resume trading"
          },
          "isCurrentlyRestricted": {
            "type": "boolean",
            "description": "Whether connection is currently restricted"
          },
          "restrictionEndTime": {
            "type": "string",
            "description": "ISO timestamp when current restriction ends"
          }
        }
      },
      "NewsRestrictionUpdate": {
        "type": "object",
        "required": [
          "connectionKey"
        ],
        "properties": {
          "connectionKey": {
            "type": "string",
            "description": "Connection identifier to update"
          },
          "enabled": {
            "type": "boolean",
            "description": "Enable or disable news restrictions"
          },
          "restrictedEvents": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "News events to restrict"
          },
          "timeBeforeMinutes": {
            "type": "number",
            "description": "Minutes before news to pause"
          },
          "notifyBeforeMinutes": {
            "type": "number",
            "description": "Minutes before restriction to notify"
          },
          "includeUSOpen": {
            "type": "boolean",
            "description": "Include US market open"
          },
          "includeUKOpen": {
            "type": "boolean",
            "description": "Include UK market open"
          },
          "includeAsiaOpen": {
            "type": "boolean",
            "description": "Include Asia market open"
          },
          "marketOpenAfterMinutes": {
            "type": "number",
            "description": "Minutes after market open to resume"
          }
        },
        "example": {
          "connectionKey": "trader@example.com|||Rithmic|||Apex Chicago Area Summary",
          "enabled": true,
          "restrictedEvents": [
            "FOMC",
            "NFP",
            "CPI"
          ],
          "timeBeforeMinutes": 15,
          "includeUSOpen": true,
          "marketOpenAfterMinutes": 30
        }
      },
      "HistoricalAccount": {
        "type": "object",
        "properties": {
          "Id": {
            "type": "string",
            "description": "Account identifier"
          },
          "Name": {
            "type": "string",
            "description": "Account display name"
          },
          "Source": {
            "type": "string",
            "description": "Data source (e.g. rithmic, tradovate)"
          },
          "ExternalId": {
            "type": "string",
            "description": "External account identifier"
          }
        }
      },
      "HistoricalTrade": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Trade ID"
          },
          "AccountId": {
            "type": "string",
            "description": "Trading account identifier"
          },
          "AccountName": {
            "type": "string",
            "description": "Trading account display name"
          },
          "Symbol": {
            "type": "string",
            "description": "Instrument symbol"
          },
          "AssetType": {
            "type": "string",
            "description": "Asset type (e.g. Future)"
          },
          "Side": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "Trade direction (0 = long, 1 = short)"
          },
          "Quantity": {
            "type": "number",
            "description": "Number of contracts"
          },
          "EntryPrice": {
            "type": "number",
            "description": "Entry fill price"
          },
          "ExitPrice": {
            "type": "number",
            "description": "Exit fill price (null if still open)"
          },
          "StopLoss": {
            "type": "number",
            "description": "Stop loss price (null if not set)"
          },
          "TakeProfit": {
            "type": "number",
            "description": "Take profit price (null if not set)"
          },
          "Commission": {
            "type": "number",
            "description": "Commission paid"
          },
          "Fees": {
            "type": "number",
            "description": "Additional fees"
          },
          "NetPnL": {
            "type": "number",
            "description": "Realized net P&L"
          },
          "LowestURPnL": {
            "type": "number",
            "description": "Maximum Adverse Excursion - worst unrealized P&L during the trade"
          },
          "HighestURPnL": {
            "type": "number",
            "description": "Maximum Favorable Excursion - best unrealized P&L during the trade"
          },
          "EntryTime": {
            "type": "string",
            "format": "date-time",
            "description": "Entry timestamp"
          },
          "ExitTime": {
            "type": "string",
            "format": "date-time",
            "description": "Exit timestamp (null if still open)"
          },
          "SetupStrategy": {
            "type": "string",
            "description": "Setup or strategy name"
          },
          "PlaybookId": {
            "type": "integer",
            "description": "Playbook ID"
          },
          "Notes": {
            "type": "string",
            "description": "Trade notes"
          },
          "Tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Trade tags"
          },
          "Source": {
            "type": "string",
            "description": "Data source (e.g. rithmic, tradovate)"
          },
          "ExternalId": {
            "type": "string",
            "description": "External trade identifier"
          },
          "Vendor": {
            "type": "string",
            "description": "Trading platform vendor"
          },
          "IsJournaled": {
            "type": "boolean",
            "description": "Whether the trade has journal entries"
          },
          "EntryTags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Journal entry tags"
          }
        }
      },
      "TradeStats": {
        "type": "object",
        "properties": {
          "overview": {
            "type": "object",
            "properties": {
              "totalTrades": {
                "type": "integer"
              },
              "winningTrades": {
                "type": "integer"
              },
              "losingTrades": {
                "type": "integer"
              },
              "breakEvenTrades": {
                "type": "integer"
              },
              "winRate": {
                "type": "number",
                "description": "Win rate percentage"
              },
              "totalPnl": {
                "type": "number"
              },
              "totalCommissions": {
                "type": "number"
              },
              "netPnl": {
                "type": "number"
              },
              "avgWin": {
                "type": "number"
              },
              "avgLoss": {
                "type": "number"
              },
              "profitFactor": {
                "type": "number"
              },
              "largestWin": {
                "type": "number"
              },
              "largestLoss": {
                "type": "number"
              },
              "avgDurationSeconds": {
                "type": "number"
              }
            }
          },
          "streaks": {
            "type": "object",
            "properties": {
              "currentStreak": {
                "type": "integer"
              },
              "currentStreakType": {
                "type": "string",
                "enum": [
                  "win",
                  "loss",
                  "none"
                ]
              },
              "longestWinStreak": {
                "type": "integer"
              },
              "longestLossStreak": {
                "type": "integer"
              }
            }
          },
          "daily": {
            "type": "object",
            "properties": {
              "bestDay": {
                "type": "object",
                "properties": {
                  "date": {
                    "type": "string"
                  },
                  "pnl": {
                    "type": "number"
                  }
                }
              },
              "worstDay": {
                "type": "object",
                "properties": {
                  "date": {
                    "type": "string"
                  },
                  "pnl": {
                    "type": "number"
                  }
                }
              },
              "avgDailyPnl": {
                "type": "number"
              },
              "profitableDays": {
                "type": "integer"
              },
              "totalDays": {
                "type": "integer"
              }
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Bad Request - Invalid input",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Unauthorized - Invalid or missing API credentials",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Forbidden - Active Journal or Thor subscription required",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Not Found - Resource does not exist",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too Many Requests - Rate limit exceeded (100 req/min)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "InternalError": {
        "description": "Internal Server Error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "Service Unavailable - Journal database is temporarily unavailable",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  },
  "paths": {
    "/hooks/tv/{token}": {
      "post": {
        "tags": [
          "Order webhook"
        ],
        "summary": "Send an order",
        "operationId": "sendWebhookOrder",
        "description": "Sends one futures order to the private webhook URL of a TradingView connection in Thor. Any program can call it: TradingView, a bot, a script or an AI agent. Thor places the order at market on every account that follows the connection, each at its own size.\n\nRules for programs: send Content-Type application/json (or text/plain); send one request at a time and wait for the answer; every request is placed, even an identical one, so after a timeout check your positions before sending again; {\"ok\":true} means received, not filled; send orders only while the connection is on; call it from a server or a script, not from a web page (no CORS). Built for strategy orders, not high-frequency trading.\n\nEntries are market orders: there are no resting limit or stop entries. Each URL keeps one net position per symbol. TradingView, PickMyTrade, TradersPost, PineConnector and Copygram message formats are also read as they are.",
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "path",
            "required": true,
            "description": "The token inside your webhook URL (Connections List, TradingView connection, Copy URL). It is the credential: keep the URL secret.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "symbol",
                  "action"
                ],
                "properties": {
                  "symbol": {
                    "type": "string",
                    "description": "Futures symbol by root (NQ, MNQ, ES, MES), continuous (NQ1!) or contract (NQZ2026). Thor uses the front-month contract each account trades.",
                    "example": "MNQ"
                  },
                  "action": {
                    "type": "string",
                    "enum": [
                      "buy",
                      "sell",
                      "close",
                      "closelong",
                      "closeshort"
                    ],
                    "description": "buy and sell open or add (and reduce an opposite position); close closes the whole position on the symbol; closelong and closeshort close that side only. Any word starting with buy, long, sell or short is read as an entry."
                  },
                  "quantity": {
                    "type": "integer",
                    "description": "Whole contracts. Left out or 0 places 1 contract. The sign is ignored: the action sets the side. A fractional size is not placed. Each follower applies its own size setting on top.",
                    "example": 1
                  },
                  "sl": {
                    "type": "number",
                    "description": "Stop loss as an absolute price. Distances and percentages are ignored."
                  },
                  "tp": {
                    "type": "number",
                    "description": "Take profit as an absolute price."
                  },
                  "update_sl": {
                    "type": "boolean",
                    "description": "true moves the stop of the open position to sl. Needs sl and an open position. Nothing is traded."
                  },
                  "update_tp": {
                    "type": "boolean",
                    "description": "true moves the target of the open position to tp. Needs tp and an open position. Nothing is traded."
                  }
                }
              },
              "examples": {
                "buy": {
                  "summary": "Buy 1 contract",
                  "value": {
                    "symbol": "MNQ",
                    "action": "buy",
                    "quantity": 1
                  }
                },
                "close": {
                  "summary": "Close the whole position",
                  "value": {
                    "symbol": "MNQ",
                    "action": "close"
                  }
                }
              }
            },
            "text/plain": {
              "schema": {
                "type": "string",
                "example": "{\"symbol\":\"MNQ\",\"action\":\"buy\",\"quantity\":1}"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{\"ok\":true}: received and passed to your connection. {\"ok\":false,\"error\":\"...\"}: not understood, nothing is placed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "ok"
                  ]
                },
                "examples": {
                  "received": {
                    "value": {
                      "ok": true
                    }
                  },
                  "notUnderstood": {
                    "value": {
                      "ok": false,
                      "error": "unrecognized action \"hold\""
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "The body is not valid JSON (Content-Type application/json)."
          },
          "404": {
            "description": "Unknown webhook URL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "error": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "ok"
                  ]
                },
                "example": {
                  "ok": false,
                  "error": "unknown webhook"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/connections/status": {
      "get": {
        "tags": [
          "Connections"
        ],
        "summary": "Get connection statuses",
        "description": "Lightweight endpoint for monitoring connection statuses (connected, failed, disabled). Returns only status-relevant fields.\n\n**Master key aggregated mode:** Omit `X-Customer-Id` to get statuses across all customers in your allowed instances (paginated). Provide `X-Customer-Id` to target a specific customer.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XCustomerId"
          },
          {
            "name": "filter",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "connected",
                "failed"
              ],
              "default": "all"
            },
            "description": "Filter connections by status. `connected`: status contains \"Connected\" (case-insensitive). `failed`: status contains \"Failed\". `all`: no filter."
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor for pagination (aggregated mode only)"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            },
            "description": "Number of customers per page (aggregated mode only)"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "title": "Single customer",
                      "properties": {
                        "connections": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "userId": {
                                "type": "string"
                              },
                              "vendorName": {
                                "type": "string"
                              },
                              "accountId": {
                                "type": "string"
                              },
                              "customName": {
                                "type": "string",
                                "nullable": true
                              },
                              "enabled": {
                                "type": "boolean"
                              },
                              "status": {
                                "type": "string",
                                "nullable": true
                              }
                            }
                          }
                        }
                      }
                    },
                    {
                      "type": "object",
                      "title": "Aggregated (master key)",
                      "properties": {
                        "customers": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "customerId": {
                                "type": "string"
                              },
                              "instanceId": {
                                "type": "integer"
                              },
                              "connections": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "userId": {
                                      "type": "string"
                                    },
                                    "vendorName": {
                                      "type": "string"
                                    },
                                    "accountId": {
                                      "type": "string"
                                    },
                                    "customName": {
                                      "type": "string",
                                      "nullable": true
                                    },
                                    "enabled": {
                                      "type": "boolean"
                                    },
                                    "status": {
                                      "type": "string",
                                      "nullable": true
                                    }
                                  }
                                }
                              }
                            }
                          }
                        },
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "nextCursor": {
                              "type": "string",
                              "nullable": true
                            },
                            "hasMore": {
                              "type": "boolean"
                            },
                            "limit": {
                              "type": "integer"
                            }
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "connections": [
                    {
                      "userId": "user_1",
                      "vendorName": "Rithmic",
                      "accountId": "trader@example.com",
                      "customName": "My TopStep Demo",
                      "enabled": true,
                      "status": "Connected (as of 2026-03-19 14:30:00 UTC)"
                    },
                    {
                      "userId": "user_2",
                      "vendorName": "Tradovate",
                      "accountId": "myaccount",
                      "customName": null,
                      "enabled": false,
                      "status": "Disabled"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid filter or cursor parameter"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/connections": {
      "get": {
        "tags": [
          "Connections"
        ],
        "summary": "List all connections",
        "description": "Retrieve all trading connections for the authenticated user. Each connection includes a unique `userId` field that you can use with PUT and DELETE endpoints.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "connections": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Connection"
                      }
                    }
                  }
                },
                "example": {
                  "connections": [
                    {
                      "userId": "user_1",
                      "vendorName": "Rithmic",
                      "vendorIcon": "/images/vendorreduced/rithmic.png",
                      "accountId": "trader@example.com",
                      "customName": "My TopStep Demo",
                      "server": "dd.rithmic.com",
                      "selectedConnection": "Demo",
                      "enabled": true,
                      "status": "Connected",
                      "groupName": "Rithmic",
                      "isCrossTradingActivated": false,
                      "isMarketDataActivated": true
                    },
                    {
                      "userId": "user_2",
                      "vendorName": "Tradovate",
                      "vendorIcon": "/images/vendorreduced/tradovate.png",
                      "accountId": "myaccount",
                      "customName": "Tradovate Live",
                      "server": "",
                      "selectedConnection": "Live",
                      "enabled": true,
                      "status": "Connected",
                      "groupName": "Tradovate",
                      "isCrossTradingActivated": false,
                      "isMarketDataActivated": false
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "Connections"
        ],
        "summary": "Create a connection",
        "description": "Add a new trading connection. Returns the `userId` which can be used to update or delete the connection.\n\n⚠️ **IMPORTANT:** After creating connection(s), you MUST call `POST /api/v1/config/save-connections` to apply your changes.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConnectionCreate"
              },
              "examples": {
                "Rithmic Paper Trading": {
                  "summary": "Rithmic Paper Trading account",
                  "value": {
                    "vendorName": "Rithmic",
                    "accountId": "trader@example.com",
                    "password": "mySecurePassword123",
                    "server": "Rithmic Paper Trading",
                    "customName": "My Personal Paper Account",
                    "groupName": "Rithmic",
                    "enabled": true,
                    "isCrossTradingActivated": false,
                    "isMarketDataActivated": true
                  }
                },
                "Apex Trader Funding": {
                  "summary": "Apex Trader Funding on Rithmic",
                  "value": {
                    "vendorName": "Apex Trader Funding",
                    "accountId": "myapexuser@email.com",
                    "password": "apexPassword123",
                    "server": "Apex Chicago Area Summary",
                    "customName": "A cool propfirm",
                    "groupName": "Rithmic",
                    "enabled": true,
                    "isCrossTradingActivated": false,
                    "isMarketDataActivated": true
                  }
                },
                "dxFeed (MyFundedFutures)": {
                  "summary": "dxFeed connection - no server needed",
                  "value": {
                    "vendorName": "MyFundedFutures",
                    "accountId": "mffu_username",
                    "password": "mffuPassword123",
                    "selectedConnection": "Demo",
                    "customName": "MFFU Demo Account",
                    "groupName": "dxFeed",
                    "enabled": true
                  }
                },
                "dxFeed (Phoenix Trader Funding)": {
                  "summary": "dxFeed connection for Phoenix Trader Funding",
                  "value": {
                    "vendorName": "dxFeed",
                    "accountId": "phoenix_user@email.com",
                    "password": "phoenixPassword123",
                    "selectedConnection": "Real",
                    "customName": "Phoenix Live Account",
                    "groupName": "Phoenix Trader Funding",
                    "enabled": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "userId": {
                      "type": "string",
                      "description": "Unique connection ID - use this for PUT/DELETE operations"
                    },
                    "vendorName": {
                      "type": "string"
                    },
                    "accountId": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "userId": "user_3",
                  "vendorName": "Rithmic",
                  "accountId": "newtrader@example.com",
                  "message": "Connection created successfully but STAGED only - it will not go live until you call POST /api/v1/config/save-connections. Do that after all your connection changes to connect the account and discover its tradable accounts."
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "description": "Duplicate connection",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "duplicate_connection",
                  "message": "A connection with this accountId and vendor already exists",
                  "existingUserId": "user_1"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/connections/{id}": {
      "put": {
        "tags": [
          "Connections"
        ],
        "summary": "Update a connection",
        "description": "Update an existing connection. Get the connection ID from GET /api/v1/connections or from the POST response when creating.\n\n⚠️ **IMPORTANT:** After updating connection(s), you MUST call `POST /api/v1/config/save-connections` to apply your changes.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Connection ID (userId) - obtained from GET /connections or POST /connections response",
            "example": "user_1"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConnectionUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "message": "Connection updated successfully"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "tags": [
          "Connections"
        ],
        "summary": "Delete a connection",
        "description": "Remove a trading connection. Get the connection ID from GET /api/v1/connections.\n\n⚠️ **IMPORTANT:** After deleting connection(s), you MUST call `POST /api/v1/config/save-connections` to apply your changes.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Connection ID (userId) - obtained from GET /connections or POST /connections response",
            "example": "user_1"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "message": "Connection deleted successfully"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "Connection is active — disable it first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "error": "connection_active",
                  "message": "To delete this connection, disable it and save first"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/accounts": {
      "get": {
        "tags": [
          "Accounts"
        ],
        "summary": "List all account configurations",
        "description": "Retrieve all leader->follower copy configurations. Each configuration includes a unique `clientId` that you can use with PUT and DELETE endpoints.\n\n`phoenixIdSource` / `convertedIdSource` identify the connection each account belongs to. They are filled in automatically at creation; if either is empty, that account was not matched to a connection and the copy engine may not handle it correctly - delete the configuration and recreate it once the connection is connected.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "accounts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Account"
                      }
                    }
                  }
                },
                "example": {
                  "accounts": [
                    {
                      "clientId": "api_1702489200000_abc123xyz",
                      "phoenixId": "7970",
                      "phoenixName": "FDS07970",
                      "convertedId": "217839188",
                      "convertedName": "217839188",
                      "phoenixIdSource": "trader@example.com",
                      "convertedIdSource": "E2710",
                      "enabled": true,
                      "multiplier": 1,
                      "stopLoss": -88390,
                      "takeProfit": 88390,
                      "isReversed": false,
                      "isAutoLiquidateLeader": false,
                      "isLiquidation": false
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "Accounts"
        ],
        "summary": "Create an account configuration",
        "description": "Create a new leader->follower copy configuration: every fill on `phoenixId` (the leader) is copied to `convertedId` (the follower). Returns a `clientId` for use with PUT and DELETE.\n\n### Getting the account IDs\nCall `GET /api/v1/connections` first. Each connection returns an `availableAccounts` array, and the `AccountId` values inside it are what you pass as `phoenixId` and `convertedId`. An id that is not in any connection's `availableAccounts` is not tradable yet - the configuration is still created, but the response carries a `warning`.\n\n### What is filled in for you\nDisplay names, the owning connection of each account, and a default symbol map for the leader/follower vendor pair are all derived automatically, so a configuration created here behaves the same as one created in the dashboard. You only need `phoenixSource` / `convertedSource` in one case: the same account ID appears under more than one connection, which is rejected with `409 ambiguous_account` and lists the candidates to choose from.\n\n### Two defaults worth checking\n- Omitting `stopLoss` / `takeProfit` means **no daily limit**, not a $1000 limit. Pass explicit values if you want the configuration to stop.\n- `multiplier` is stored exactly as sent. Copying between futures and CFD accounts (cTrader, MetaTrader, DXtrade) needs a pre-scaled value - see the field description.\n\n⚠️ **IMPORTANT:** Creating a configuration only stages it. After creating configuration(s), you MUST call `POST /api/v1/config/save-accounts` to apply your changes - nothing is copied until you do.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AccountCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "clientId": {
                      "type": "string",
                      "description": "Unique configuration ID - use this for PUT/DELETE operations"
                    },
                    "phoenixId": {
                      "type": "string"
                    },
                    "phoenixName": {
                      "type": "string",
                      "description": "Resolved leader display name (falls back to the id when the account is not discovered yet)"
                    },
                    "convertedId": {
                      "type": "string"
                    },
                    "convertedName": {
                      "type": "string",
                      "description": "Resolved follower display name"
                    },
                    "copyMode": {
                      "type": "string",
                      "description": "The copy mode actually stored, after any NinjaTrader downgrade"
                    },
                    "warning": {
                      "type": "string",
                      "description": "Present only when an account id did not match any discovered account, so its owning connection could not be resolved. The configuration is still staged, but verify the id before calling save-accounts."
                    },
                    "notice": {
                      "type": "string",
                      "description": "Present only when copyMode was downgraded from \"allorders\" to \"fillsonly\" for a NinjaTrader follower"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "clientId": "api_1702489200000_abc123xyz",
                  "phoenixId": "APEX-12345-Leader",
                  "phoenixName": "APEX-12345",
                  "convertedId": "APEX-67890-Follower",
                  "convertedName": "APEX-67890",
                  "copyMode": "fillsonly",
                  "message": "Account configuration created successfully but STAGED only - it will not copy until you call POST /api/v1/config/save-accounts. Do that after all your account changes to apply them to the running instance. Use this clientId to update or delete the configuration."
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "All Orders copy mode is not unlocked for this customer",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": "allorders_locked",
                  "message": "\"allorders\" copy mode is not enabled for this customer. It must be unlocked first (contact support); until then use \"fillsonly\" (the default and recommended mode)."
                }
              }
            }
          },
          "409": {
            "description": "Duplicate configuration, or an account id that exists under more than one connection",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "duplicate_configuration": {
                    "summary": "This leader/follower pair is already configured",
                    "value": {
                      "error": "duplicate_configuration",
                      "message": "A configuration with this leader and follower already exists",
                      "existingClientId": "api_1702489100000_xyz789abc"
                    }
                  },
                  "ambiguous_account": {
                    "summary": "The account id exists under several connections - say which one",
                    "value": {
                      "error": "ambiguous_account",
                      "message": "convertedId \"217839188\" exists under 2 connections, so the owning connection cannot be determined. Pass convertedSource with the accountId of the connection you mean (see GET /api/v1/connections).",
                      "candidates": [
                        "trader@example.com",
                        "E2710"
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/accounts/{id}": {
      "put": {
        "tags": [
          "Accounts"
        ],
        "summary": "Update an account configuration",
        "description": "Update an existing account configuration. Get the configuration ID from GET /api/v1/accounts or from the POST response when creating.\n\n⚠️ **Always pass a `clientId`.** For backwards compatibility the id also matches a leader or follower account ID, but that updates only the FIRST configuration using that account - if the account appears in several configurations the rest are silently left unchanged.\n\n⚠️ **IMPORTANT:** After updating configuration(s), you MUST call `POST /api/v1/config/save-accounts` to apply your changes.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Configuration ID (clientId) from GET /accounts or the POST /accounts response. A leader or follower account ID is also accepted but matches only the first configuration using it.",
            "example": "api_1702489200000_abc123xyz"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AccountUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "message": "Account configuration updated successfully"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "tags": [
          "Accounts"
        ],
        "summary": "Delete an account configuration",
        "description": "Remove an account configuration. Get the configuration ID from GET /api/v1/accounts.\n\n⚠️ **This endpoint matches on the leader or follower account ID, not on `clientId`.** It deletes the FIRST configuration using that account, so if an account appears in several configurations, call it once per configuration and re-read GET /api/v1/accounts in between to confirm which one went.\n\n⚠️ **IMPORTANT:** After deleting configuration(s), you MUST call `POST /api/v1/config/save-accounts` to apply your changes.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Leader (phoenixId) or follower (convertedId) account ID from GET /accounts. Note: unlike PUT, a clientId is NOT accepted here.",
            "example": "217839188"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "message": "Account configuration deleted successfully"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/positions": {
      "get": {
        "tags": [
          "Positions"
        ],
        "summary": "Get current positions",
        "description": "Retrieve all current open positions for today. This data resets daily and only contains the current trading day. For historical trade data, use GET /api/v1/historical/trades.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "positions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Position"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/positions/orders": {
      "get": {
        "tags": [
          "Positions"
        ],
        "summary": "Get working orders",
        "description": "Retrieve all pending/working orders for today. This data resets daily.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "orders": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/positions/closed": {
      "get": {
        "tags": [
          "Positions"
        ],
        "summary": "Get closed positions",
        "description": "Retrieve positions closed during the current trading day. This data resets daily. For full historical trade data, use GET /api/v1/historical/trades.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "closedPositions": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/positions/running": {
      "get": {
        "tags": [
          "Positions"
        ],
        "summary": "Get running positions",
        "description": "Retrieve only currently open (running) positions. Equivalent to filtering /api/v1/positions for entries where IsClosed is false. This data resets daily.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "positions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Position"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/positions/flatten": {
      "post": {
        "tags": [
          "Positions"
        ],
        "summary": "Flatten all positions",
        "description": "Flatten (close) all open positions across all accounts. This is an all-or-nothing operation. Only affects today's active positions.",
        "responses": {
          "200": {
            "description": "Command sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "message": "Flatten all positions command sent"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/positions/close": {
      "post": {
        "tags": [
          "Positions"
        ],
        "summary": "Close a position",
        "description": "Close the open positions of an account: all of them, only those on `symbol`, or only the one whose size is `quantity`. Positions close whole; partial closes are not supported. One close command is sent per position, about 3 seconds apart. Returns 404 when no open position matches.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "accountId"
                ],
                "properties": {
                  "accountId": {
                    "type": "string",
                    "description": "The position's AccountId from GET /api/v1/positions"
                  },
                  "symbol": {
                    "type": "string",
                    "description": "Optional: only positions on this symbol"
                  },
                  "quantity": {
                    "type": "number",
                    "description": "Optional: only the position of exactly this size"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Command sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    },
                    "sent": {
                      "type": "integer",
                      "description": "Number of positions a close command was sent for"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "message": "Close position command sent",
                  "sent": 1
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/positions/cancel": {
      "post": {
        "tags": [
          "Positions"
        ],
        "summary": "Cancel an order",
        "description": "Cancel a working order. Returns 404 when no working order matches.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "accountId",
                  "orderId"
                ],
                "properties": {
                  "accountId": {
                    "type": "string",
                    "description": "The order's AccountId from GET /api/v1/positions/orders"
                  },
                  "orderId": {
                    "type": "string",
                    "description": "The order's id from GET /api/v1/positions/orders"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Command sent"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/risk/lockouts": {
      "get": {
        "tags": [
          "Risk Management"
        ],
        "summary": "Get locked accounts",
        "description": "Get all currently locked accounts. Locked accounts cannot trade until the lock expires or is manually removed.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "lockedAccounts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/LockedAccount"
                      }
                    }
                  }
                },
                "example": {
                  "lockedAccounts": [
                    {
                      "accountId": "APEX-12345",
                      "unlockTime": 1702500000000,
                      "lockedAt": 1702496400000,
                      "reason": "Daily loss limit",
                      "remainingMinutes": 45
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "tags": [
          "Risk Management"
        ],
        "summary": "Lock accounts",
        "description": "Lock one or more accounts for a specified duration. This disables all trading configurations using these accounts.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LockAccountsRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Accounts locked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    },
                    "unlockTime": {
                      "type": "number"
                    },
                    "lockedAccounts": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "message": "Locked 2 account(s) for 60 minutes",
                  "unlockTime": 1702500000000,
                  "lockedAccounts": [
                    "APEX-12345",
                    "APEX-67890"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "tags": [
          "Risk Management"
        ],
        "summary": "Unlock accounts",
        "description": "Unlock one or more accounts before their lock expires",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "accountIds"
                ],
                "properties": {
                  "accountIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Array of account IDs to unlock"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accounts unlocked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    },
                    "unlockedAccounts": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "message": "Unlocked 2 account(s)",
                  "unlockedAccounts": [
                    "APEX-12345",
                    "APEX-67890"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/risk/news": {
      "get": {
        "tags": [
          "Risk Management"
        ],
        "summary": "Get news restrictions",
        "description": "Get all news trading restrictions configured for your connections. News restrictions automatically pause trading around high-impact economic events.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "newsRestrictions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/NewsRestriction"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "put": {
        "tags": [
          "Risk Management"
        ],
        "summary": "Update news restriction",
        "description": "Create or update news restriction for a connection. Common restrictedEvents: FOMC, NFP, CPI, PPI, GDP, Retail Sales, ISM Manufacturing",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NewsRestrictionUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Restriction updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    },
                    "restriction": {
                      "$ref": "#/components/schemas/NewsRestriction"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/risk/news/{connectionKey}": {
      "delete": {
        "tags": [
          "Risk Management"
        ],
        "summary": "Delete news restriction",
        "description": "Remove news restriction for a connection",
        "parameters": [
          {
            "name": "connectionKey",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "URL-encoded connection key"
          }
        ],
        "responses": {
          "200": {
            "description": "Restriction removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/config/save-connections": {
      "post": {
        "tags": [
          "Configuration"
        ],
        "summary": "Save & apply connection changes",
        "description": "**IMPORTANT:** You MUST call this endpoint after making changes to connections (create, update, or delete). This triggers Thor to reload and apply your connection changes. Without calling this, your changes will be stored but not active.\n\n**Workflow:**\n1. Make your connection changes (POST/PUT/DELETE /api/v1/connections)\n2. Call this endpoint to apply all changes at once",
        "responses": {
          "200": {
            "description": "Connections saved and applied",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "message": "Connections saved and applied"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/config/save-accounts": {
      "post": {
        "tags": [
          "Configuration"
        ],
        "summary": "Save & apply account config changes",
        "description": "**IMPORTANT:** You MUST call this endpoint after making changes to account configurations (create, update, or delete). This triggers Thor to reload and apply your account configuration changes. Without calling this, your changes will be stored but not active.\n\n**Workflow:**\n1. Make your account config changes (POST/PUT/DELETE /api/v1/accounts)\n2. Call this endpoint to apply all changes at once",
        "responses": {
          "200": {
            "description": "Account configurations saved and applied",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "message": "Account configurations saved and applied"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/config/sync": {
      "post": {
        "tags": [
          "Configuration"
        ],
        "summary": "Sync accounts from trading platforms",
        "description": "Fetches the latest account list from your connected trading platforms.\n\n**⚠️ ONLY use this after a reset of a newly purchased account.** This refreshes the available accounts from your trading platforms - it does NOT save any of your configurations.\n\n**This endpoint does NOT save connections or account configurations.** Use `save-connections` or `save-accounts` for that.",
        "responses": {
          "200": {
            "description": "Sync triggered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "message": "Account sync from trading platforms triggered"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/oauth/initiate": {
      "post": {
        "tags": [
          "OAuth (Partner)"
        ],
        "summary": "Begin the partner onboarding flow for a vendor",
        "description": "Begins the partner onboarding flow for one of the supported vendors. The response shape depends on the vendor.\n\n**Supported vendors:** Tradovate, cTrader, Tradovate Live, cTrader Live, NinjaTrader\n\n**OAuth vendors (Tradovate, cTrader, …):**\n1. Pass `redirectUri`. Response is `{ oauthUrl, expiresIn }`.\n2. Open `oauthUrl` as a popup window.\n3. User logs in on the vendor site.\n4. Popup redirects to your `redirectUri` with `?vendor=...&access_token=...`.\n5. Use the access token to create a connection via `POST /api/v1/connections/oauth`.\n\n**NinjaTrader (provisioned-credentials flow):**\n1. `redirectUri` is **not** required and is ignored if supplied. `X-Customer-Id` is required for master keys.\n2. Response is `{ vendor:\"NinjaTrader\", credentials:{ userId, apiKey }, addonUrl, addonInstallPath, isNew }`.\n3. Show the credentials to the user and link them to `addonUrl` to download the NinjaScript addon ZIP.\n4. The user installs the addon into `addonInstallPath`, restarts NinjaTrader, and pastes the credentials into the addon.\n5. Register the connection by calling `POST /api/v1/connections/oauth` with `accessToken=<apiKey>` and `accountName=<userId UUID>`.\n\nThe NinjaTrader credentials are stable per customer (same `userId`/`apiKey` returned on subsequent calls). `isNew` is `true` only on the first ever provisioning.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "vendorName"
                ],
                "properties": {
                  "vendorName": {
                    "type": "string",
                    "enum": [
                      "Tradovate",
                      "cTrader",
                      "Tradovate Live",
                      "cTrader Live",
                      "NinjaTrader"
                    ],
                    "description": "The trading platform to connect"
                  },
                  "redirectUri": {
                    "type": "string",
                    "format": "uri",
                    "description": "OAuth-only: callback URL where the popup will be redirected with the access token. Required for Tradovate/cTrader; ignored for NinjaTrader."
                  }
                }
              },
              "examples": {
                "tradovate": {
                  "summary": "Tradovate (OAuth)",
                  "value": {
                    "vendorName": "Tradovate",
                    "redirectUri": "https://yoursite.com/oauth/callback"
                  }
                },
                "ninjatrader": {
                  "summary": "NinjaTrader (provisioned)",
                  "value": {
                    "vendorName": "NinjaTrader"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OAuth URL generated, or NinjaTrader credentials provisioned",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "title": "OAuth response",
                      "properties": {
                        "oauthUrl": {
                          "type": "string",
                          "description": "URL to open as a popup for the user to authenticate"
                        },
                        "expiresIn": {
                          "type": "integer",
                          "description": "Seconds until the URL expires"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "title": "NinjaTrader response",
                      "properties": {
                        "vendor": {
                          "type": "string",
                          "enum": [
                            "NinjaTrader"
                          ]
                        },
                        "credentials": {
                          "type": "object",
                          "properties": {
                            "userId": {
                              "type": "string",
                              "description": "NinjaTrader User ID (UUID) — pass as accountName to /api/v1/connections/oauth"
                            },
                            "apiKey": {
                              "type": "string",
                              "description": "NinjaTrader API key (ninja_…) — pass as accessToken to /api/v1/connections/oauth"
                            }
                          }
                        },
                        "addonUrl": {
                          "type": "string",
                          "format": "uri",
                          "description": "Direct download URL for the NinjaScript addon ZIP. May be branded per partner."
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "oauth": {
                    "summary": "OAuth response",
                    "value": {
                      "oauthUrl": "https://trader.tradovate.com/oauth?response_type=code&client_id=...&redirect_uri=...&state=...",
                      "expiresIn": 600
                    }
                  },
                  "ninjatrader": {
                    "summary": "NinjaTrader response",
                    "value": {
                      "vendor": "NinjaTrader",
                      "credentials": {
                        "userId": "c9af84ca-43ef-49ad-ada3-4baddefab814",
                        "apiKey": "ninja_c3c06b0c9773c5029cb494d7ae49de63fd116fb611659f09"
                      },
                      "addonUrl": "https://app.thortradecopier.com/downloads/ThorTradeCopierNinjaAddon.zip"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "description": "Upstream NinjaTrader provisioning relay unavailable"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/api/v1/connections/oauth": {
      "post": {
        "tags": [
          "OAuth (Partner)"
        ],
        "summary": "Register a connection from /oauth/initiate output",
        "description": "Register a trading connection using the credentials returned by `/api/v1/oauth/initiate`. Supports both OAuth vendors and NinjaTrader.\n\n**OAuth vendors (Tradovate, cTrader, …):** `accessToken` is the OAuth access token from the popup callback; `accountName` (Tradovate only) is the account name returned by the callback.\n\n**NinjaTrader:** `accessToken` is the API key (`ninja_…`) and `accountName` is the User ID (UUID) — both returned by `/api/v1/oauth/initiate`.\n\n**Important:** After creating the connection, you MUST call `POST /api/v1/config/save-connections` to activate it.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "vendorName",
                  "accessToken"
                ],
                "properties": {
                  "vendorName": {
                    "type": "string",
                    "enum": [
                      "Tradovate",
                      "cTrader",
                      "Tradovate Live",
                      "cTrader Live",
                      "NinjaTrader"
                    ],
                    "description": "The vendor from the initiate step"
                  },
                  "accessToken": {
                    "type": "string",
                    "description": "OAuth: the access token from the popup redirect. NinjaTrader: the API key (ninja_…) returned by /api/v1/oauth/initiate."
                  },
                  "accountName": {
                    "type": "string",
                    "description": "Tradovate: account name from the OAuth callback (used to build TRDV_<name>). NinjaTrader: the User ID (UUID) returned by /api/v1/oauth/initiate (REQUIRED for NinjaTrader)."
                  },
                  "customName": {
                    "type": "string",
                    "description": "Optional custom display name for this connection"
                  }
                }
              },
              "examples": {
                "tradovate": {
                  "summary": "Tradovate",
                  "value": {
                    "vendorName": "Tradovate",
                    "accessToken": "eyJhbGciOi...",
                    "accountName": "MyAccount",
                    "customName": "My Trading Account"
                  }
                },
                "ninjatrader": {
                  "summary": "NinjaTrader",
                  "value": {
                    "vendorName": "NinjaTrader",
                    "accessToken": "ninja_c3c06b0c9773c5029cb494d7ae49de63fd116fb611659f09",
                    "accountName": "c9af84ca-43ef-49ad-ada3-4baddefab814",
                    "customName": "NinjaTrader"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Connection created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "userId": {
                      "type": "string"
                    },
                    "vendorName": {
                      "type": "string"
                    },
                    "accountId": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "userId": "user_3",
                  "vendorName": "Tradovate",
                  "accountId": "TRDV_MyAccount",
                  "message": "Connection created. Call POST /api/v1/config/save-connections to activate."
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "description": "duplicate_connection — a NinjaTrader connection with this User ID already exists"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/vendors": {
      "get": {
        "tags": [
          "Vendors"
        ],
        "summary": "List available vendors",
        "description": "Retrieve the list of supported trading platform vendors and their connection types.\n\nThe response also includes the list of available servers, particularly for vendors that require a server selection (e.g., Rithmic). For Rithmic, servers are grouped under a `brokers` array, where each broker (Rithmic Generic, Apex Trader Funding, UProfit, LegendsTrading, etc.) exposes its own list of valid `servers` to use when creating a connection.\n\nTradeLocker (`requiresServer: true`, connections Demo or Live) takes the TradeLocker server name from the prop firm's credentials email, which has no fixed list. iFX is `leaderOnly`: its accounts can be copied from but never into, and it needs no server.\n\nExample (truncated):\n```json\n{\n  \"vendors\": [\n    {\n      \"name\": \"Tradovate\",\n      \"authType\": \"oauth\",\n      \"requiresServer\": false,\n      \"connections\": [\"OAuth\"]\n    },\n    {\n      \"name\": \"Rithmic\",\n      \"authType\": \"credentials\",\n      \"requiresServer\": true,\n      \"brokers\": [\n        {\n          \"label\": \"Rithmic (Generic)\",\n          \"vendorName\": \"Rithmic\",\n          \"servers\": [\n            \"Rithmic Paper Trading Chicago Area\",\n            \"Rithmic 01 Chicago Area\",\n            \"Rithmic 01 Europe\"\n          ]\n        },\n        {\n          \"label\": \"Apex Trader Funding\",\n          \"vendorName\": \"Rithmic\",\n          \"servers\": [\"Apex Chicago Area Summary\", \"Apex Europe\", \"Apex Tokyo\"]\n        }\n      ]\n    }\n  ]\n}\n```",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "vendors": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string",
                            "description": "Vendor name"
                          },
                          "authType": {
                            "type": "string",
                            "description": "Authentication type. \"oauth\" vendors use POST /api/v1/oauth/initiate; \"provisioned\" vendors (e.g. NinjaTrader) also use /oauth/initiate but receive credentials + an addon URL instead of an OAuth URL; \"credentials\" vendors use POST /api/v1/connections directly."
                          },
                          "requiresServer": {
                            "type": "boolean",
                            "description": "Whether the vendor requires a server selection when creating a connection"
                          },
                          "connections": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "Available connection types"
                          },
                          "servers": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "description": "List of valid server names for vendors with a single server set"
                          },
                          "leaderOnly": {
                            "type": "boolean",
                            "description": "Present and true for vendors that can only be copied FROM (e.g. iFX): use their accounts as leaders, never as followers."
                          },
                          "brokers": {
                            "type": "array",
                            "description": "For multi-broker vendors (e.g., Rithmic), the list of brokers with their own server lists",
                            "items": {
                              "type": "object",
                              "properties": {
                                "label": {
                                  "type": "string",
                                  "description": "Broker display label (e.g., \"Apex Trader Funding\")"
                                },
                                "vendorName": {
                                  "type": "string",
                                  "description": "Underlying vendor name to use when creating a connection"
                                },
                                "servers": {
                                  "type": "array",
                                  "items": {
                                    "type": "string"
                                  },
                                  "description": "Valid server names for this broker"
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/status": {
      "get": {
        "tags": [
          "Status"
        ],
        "summary": "Get system status",
        "security": [],
        "description": "Public endpoint (no API key required) that returns the current operational status of the Thor platform — the same data shown on the dashboard System Status page. Use the top-level `operational` boolean for a quick health check, or inspect the per-service `services` array and any active `incidents`. The response is cached for ~20 seconds. For historical uptime data, use `GET /api/status-history`.\n\nExample:\n```json\n{\n  \"operational\": true,\n  \"status\": \"operational\",\n  \"overall\": { \"status\": \"operational\" },\n  \"services\": [ { \"name\": \"Copy Engine\", \"status\": \"operational\" } ],\n  \"incidents\": [],\n  \"updatedAt\": \"2026-06-20T14:37:00.000Z\",\n  \"source\": \"thortradecopier.com/status.json\"\n}\n```",
        "responses": {
          "200": {
            "description": "Current system status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "operational": {
                      "type": "boolean",
                      "description": "True when the overall status is operational/good/normal"
                    },
                    "status": {
                      "type": "string",
                      "description": "Overall status string (e.g. operational, degraded, maintenance, major_outage)"
                    },
                    "overall": {
                      "type": "object",
                      "description": "Full overall-status object from the status feed"
                    },
                    "services": {
                      "type": "array",
                      "description": "Per-service statuses",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "incidents": {
                      "type": "array",
                      "description": "Active or recent incidents",
                      "items": {
                        "type": "object"
                      }
                    },
                    "updatedAt": {
                      "type": "string",
                      "format": "date-time",
                      "description": "When this status was last fetched from the source"
                    },
                    "source": {
                      "type": "string",
                      "description": "Upstream status source"
                    },
                    "stale": {
                      "type": "boolean",
                      "description": "Present and true only when the upstream was unreachable and a cached value is being served"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "description": "Status temporarily unavailable"
          }
        }
      }
    },
    "/api/v1/historical/accounts": {
      "get": {
        "tags": [
          "Historical Trades"
        ],
        "summary": "List trading accounts",
        "description": "Retrieve all unique trading accounts that have historical trade data. Requires an active Journal or Thor subscription.",
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "accounts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/HistoricalAccount"
                      }
                    }
                  }
                },
                "example": {
                  "accounts": [
                    {
                      "Id": "APEX-12345",
                      "Name": "Apex Funded Account",
                      "Source": "rithmic",
                      "ExternalId": ""
                    },
                    {
                      "Id": "TRD-67890",
                      "Name": "Tradovate Sim",
                      "Source": "tradovate",
                      "ExternalId": "ext_123"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/api/v1/historical/trades/stats": {
      "get": {
        "tags": [
          "Historical Trades"
        ],
        "summary": "Get trade statistics",
        "description": "Retrieve aggregated trade statistics including win rate, P&L, streaks, and daily performance. Supports filtering by date range, symbol, playbook, and account.",
        "parameters": [
          {
            "name": "dateFrom",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Start date filter (YYYY-MM-DD)",
            "example": "2024-01-01"
          },
          {
            "name": "dateTo",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "End date filter (YYYY-MM-DD)",
            "example": "2024-12-31"
          },
          {
            "name": "symbol",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by symbol (partial match)",
            "example": "ES"
          },
          {
            "name": "playbook",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by playbook name",
            "example": "Breakout"
          },
          {
            "name": "account",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by account ID",
            "example": "APEX-12345"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TradeStats"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/api/v1/historical/trades/journaled-ids": {
      "get": {
        "tags": [
          "Historical Trades"
        ],
        "summary": "Get journaled trade IDs",
        "description": "Retrieve the IDs of trades that have been journaled (have notes, tags, playbook, or rating). Useful for checking which trades have journal entries without fetching full trade data.",
        "parameters": [
          {
            "name": "ids",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated trade IDs to check. If omitted, returns count of all journaled trades.",
            "example": "1,2,3,4,5"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "journaledIds": {
                      "type": "array",
                      "items": {
                        "type": "integer"
                      },
                      "description": "IDs that have journal entries (when ids param provided)"
                    },
                    "count": {
                      "type": "integer",
                      "description": "Total number of journaled trades (when ids param omitted)"
                    }
                  }
                },
                "example": {
                  "journaledIds": [
                    1,
                    3,
                    5
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/api/v1/historical/trades": {
      "get": {
        "tags": [
          "Historical Trades"
        ],
        "summary": "List historical trades",
        "description": "Retrieve historical trades with filtering and pagination. Supports cursor-based pagination for efficient traversal of large datasets, as well as offset-based pagination.\n\n**Pagination:** Use `cursor` from the response `nextCursor` field for cursor-based pagination, or use `page` + `limit` for offset-based pagination. Cursor-based pagination is recommended for large datasets.",
        "parameters": [
          {
            "name": "dateFrom",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Start date filter (YYYY-MM-DD)",
            "example": "2024-01-01"
          },
          {
            "name": "dateTo",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "End date filter (YYYY-MM-DD)",
            "example": "2024-12-31"
          },
          {
            "name": "symbol",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by symbol (partial match)",
            "example": "ES"
          },
          {
            "name": "side",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "Buy",
                "Sell"
              ]
            },
            "description": "Filter by trade direction"
          },
          {
            "name": "account",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by account ID",
            "example": "APEX-12345"
          },
          {
            "name": "playbook",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by playbook name"
          },
          {
            "name": "tags",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by tag (partial match)"
          },
          {
            "name": "minPnl",
            "in": "query",
            "schema": {
              "type": "number"
            },
            "description": "Minimum P&L filter"
          },
          {
            "name": "maxPnl",
            "in": "query",
            "schema": {
              "type": "number"
            },
            "description": "Maximum P&L filter"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "maximum": 500
            },
            "description": "Number of trades per page (max 500)"
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Cursor for cursor-based pagination (from nextCursor in response)"
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            },
            "description": "Page number for offset-based pagination"
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "entry_time",
                "exit_time",
                "pnl",
                "symbol"
              ],
              "default": "entry_time"
            },
            "description": "Sort field"
          },
          {
            "name": "order",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            },
            "description": "Sort order"
          }
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "trades": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/HistoricalTrade"
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "page": {
                          "type": "integer",
                          "description": "Current page number"
                        },
                        "limit": {
                          "type": "integer",
                          "description": "Items per page"
                        },
                        "total": {
                          "type": "integer",
                          "description": "Total matching trades (omitted with cursor pagination)"
                        },
                        "totalPages": {
                          "type": "integer",
                          "description": "Total pages (omitted with cursor pagination)"
                        },
                        "hasMore": {
                          "type": "boolean",
                          "description": "Whether more pages exist"
                        },
                        "nextCursor": {
                          "type": "string",
                          "description": "Cursor for next page (use with cursor param)"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/api/v1/customers": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "List customers (Master Key only)",
        "description": "Returns a paginated list of customers across the master key's allowed instances. Requires a master API key.",
        "parameters": [
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor for pagination"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 200
            },
            "description": "Number of results per page"
          },
          {
            "name": "instanceId",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "description": "Filter by specific instance ID"
          },
          {
            "name": "customerId",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by exact customer ID"
          },
          {
            "name": "email",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by email prefix"
          },
          {
            "name": "search",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Search across email and name"
          },
          {
            "name": "createdAfter",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Filter: created after this date"
          },
          {
            "name": "createdBefore",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Filter: created before this date"
          }
        ],
        "responses": {
          "200": {
            "description": "Customer list with pagination"
          },
          "403": {
            "description": "Master key required or instance not allowed"
          }
        }
      }
    },
    "/api/v1/customers/{customerId}": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Get customer detail (Master Key only)",
        "description": "Returns detailed information for a specific customer. Requires a master API key.",
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "The customer ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Customer details"
          },
          "403": {
            "description": "Master key required or instance not allowed"
          },
          "404": {
            "description": "Customer not found"
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Order webhook",
      "description": "Send futures orders from TradingView, a bot, a script or an AI agent to one private URL. Thor places each order on every account that follows it."
    },
    {
      "name": "Connections",
      "description": "Manage trading platform connections. Remember to call save-connections after changes!"
    },
    {
      "name": "Accounts",
      "description": "Manage leader-follower copy configurations. Remember to call save-accounts after changes!"
    },
    {
      "name": "Positions",
      "description": "View and manage today's positions and orders. Position data resets daily. For historical trade data, use the Historical Trades endpoints."
    },
    {
      "name": "Risk Management",
      "description": "Account lockouts and news trading restrictions"
    },
    {
      "name": "Configuration",
      "description": "Save changes and sync with trading platforms"
    },
    {
      "name": "Vendors",
      "description": "Available trading platform vendors and connection types"
    },
    {
      "name": "Status",
      "description": "Public system status and operational health. No API key required."
    },
    {
      "name": "Historical Trades",
      "description": "Read-only access to historical trade data from the Journal database. Requires an active Journal or Thor subscription. For today's live positions, use the Positions endpoints."
    },
    {
      "name": "Customers",
      "description": "List and inspect customers under a master API key."
    }
  ],
  "externalDocs": {
    "description": "Developer guide: the order webhook and the REST API",
    "url": "https://thortradecopier.com/developers"
  }
}
