User API: Account, Trading and Schema Endpoints
This article is the reference for the User API endpoints that describe your account: what your API client may do, your broker accounts and their balances, your strategy groups and symbols, account-wide profit and loss, the trading pause switch, and the two schema endpoints your program reads before it builds anything.
Every path below is under https://api.tradesteward.com/rest/v1, and every request carries Authorization: Bearer <token>. Every answer is the JSON envelope described in Your First Request: ok, data, error, apiVersion and requestId. The examples show only data. Money is always a decimal string in US dollars, such as "1250.00". Times in data are UTC in ISO 8601 form unless a field says otherwise. Dates you send are YYYY-MM-DD.
The plan you need is in Use the tradeSteward API From Your Own Code. Each section names the permission it needs. Permissions are chosen on the Connected Apps page; see Permissions and Limits.
Reads are limited per access token and per account. GET /performance also counts toward the bot reads limit, like the bot endpoints in the next article. A refused request is 429 rate_limited with a Retry-After header.
Your account and connection
GET /me What this client may do
The first request a program should make. It answers who the connection is, what it may do, which plan the account has, and the reference data a client needs before building a bot: the broker accounts, the strategy groups, the symbols and whether trading is paused. Every client may call it, whatever permissions it holds. Each section appears only when the client is entitled to it and holds the permission that section needs, so a client without bots.view sees no accounts, strategies, symbols, trading or bot limits. Operation id: getAccountContext.
Permission: none.
What it returns:
| Field | Meaning |
|---|---|
connection | kind (api_client for your own code, connector for an AI assistant) and label, the name you gave the client. |
account | displayName (your first name) and timezone, always America/New_York: every time the website shows is US Eastern. |
entitlements | bots (the plan includes API access to bots: reading them, creating them, editing bots that are not live, and pausing and resuming trading), botsWrite (the plan includes turning bots on and off and editing live bots: Trader Unlimited), backtick (the plan includes Backticks), botsTier and backtickTier (the plan names). |
permissions | A map of every permission key to true or false: what this client was given, capped by your account limits and by your plan. Below Trader Unlimited, bots.enable, bots.disable and bots.edit.live are false even when the client was given them. |
constraints | The client's limits: accounts (a list of account numbers it may use, or null for all) and onlyOwnBots (true when it may touch only the bots it created). |
limits | maxBots, the number of bots your plan allows to hold a slot, and enabledBots, how many hold one now, counted account-wide exactly as the enable check counts them (an enabled bot, an enabled entry of an enabled Multi-Entry bot, or any bot with an open trade). maxBots minus enabledBots is the real headroom. With a Backtick plan, backtick holds maxPositions and queuedPositionYears. |
dictionaryVersion | The version of the settings dictionary (see GET /schema/bot-settings). When it changes, read the dictionary again. |
accounts, strategies, symbols | The same lists as GET /accounts, GET /strategies and GET /symbols below. |
trading | paused and pauseLocked, as GET /trading/status returns them; null when the trading state could not be read. |
{
"connection": {"kind": "api_client", "label": "Portfolio dashboard"},
"account": {"displayName": "Jordan", "timezone": "America/New_York"},
"entitlements": {"bots": true, "botsWrite": true, "backtick": true,
"botsTier": "Trader Unlimited", "backtickTier": "Backtick Professional"},
"permissions": {"bots.view": true, "bots.settings.view": true, "bots.create": true,
"bots.enable": false, "trading.pause": true, "trading.resume": false},
"constraints": {"accounts": null, "onlyOwnBots": false},
"limits": {"maxBots": 250, "enabledBots": 7,
"backtick": {"maxPositions": 250, "queuedPositionYears": 8000}},
"dictionaryVersion": "e3ebc41ac673",
"accounts": [{"accountNum": "12344821", "broker": "schwab",
"nickname": "Schwab Individual x821", "paper": false, "botsEnabled": true}],
"strategies": [{"id": "SPXPCS", "name": "SPX put credit spreads", "enabled": true}],
"symbols": ["SPY", "$SPX.X", "QQQ"],
"trading": {"paused": false, "pauseLocked": false}
}
Refusals: 401 invalid_token; 403 entitlement_required when the plan includes no API access at all.
Broker accounts
GET /accounts List the broker accounts this client may use
Your open broker accounts, in the order the website lists them, leaving out any account the client is not allowed to use. A bot is created on one of these account numbers. Operation id: listAccounts.
Permission: any one of bots.view, trades.view or trades.strategy.edit.
What it returns: accounts, a list of objects with accountNum (the number you send to other endpoints), broker (the broker in lower case: schwab, tasty for tastytrade, tradier or tradestation), nickname (the account's name as you entered it, with any account number in it masked to its last three characters, such as x821; an account with no name is Account x821), paper (true for a paper trading account) and botsEnabled (whether the account is turned on for bot trading; a bot cannot be enabled on an account that is off).
Refusals: 403 permission_denied or 403 entitlement_required.
GET /accounts/{accountNum}/balances Daily balance history of one account
One point per day from the nightly account summary: the end-of-day net liquidation value and that day's deposits and withdrawals, plus available buying power from that day's latest stored balance record. Today's point appears after the nightly summary runs. Operation id: getAccountBalances.
Permission: accounts.balances.view (View account balance history).
| Name | Type | Required | Meaning |
|---|---|---|---|
accountNum (path) | string | Yes | An account number from GET /accounts. |
from (query) | date | No | First date, YYYY-MM-DD. Left out, it is 2000-01-01, so the history starts at its beginning; the answer's from then reads 2000-01-01. |
to (query) | date | No | Last date, YYYY-MM-DD. Defaults to today (US Eastern). |
What it returns: accountNum, from and to as used, points (oldest first, each with date, nlv, buyingPowerAvailable and depositsWithdrawals, where buying power is a decimal string in US dollars or null when the balance record is missing or its buying power was not reported, and deposits are positive, withdrawals negative and "0.00" is a day with neither) and truncated. A response carries at most 1000 points. When the range holds more, the most recent 1000 are returned and truncated is true: ask again with an earlier to date for the rest.
{
"accountNum": "12344821", "from": "2026-10-01", "to": "2026-10-03",
"points": [
{"date": "2026-10-01", "nlv": "104210.55", "buyingPowerAvailable": "84210.55", "depositsWithdrawals": "0.00"},
{"date": "2026-10-02", "nlv": "104890.10", "buyingPowerAvailable": "0.00", "depositsWithdrawals": "0.00"},
{"date": "2026-10-03", "nlv": "109612.40", "buyingPowerAvailable": null, "depositsWithdrawals": "5000.00"}
],
"truncated": false
}
Refusals: 400 bad_request when a date is malformed or from is after to; 404 not_found for an account that is not yours or that the client may not use; 403 permission_denied.
Strategy groups and symbols
GET /strategies Your strategy groups
Every strategy group in your account, enabled ones first, then by name. A bot is created under one group's strategy ID, and the bot list can be filtered by it. Operation id: listStrategies.
Permission: any one of bots.view, trades.view or trades.strategy.edit.
What it returns: strategies, a list of strategy objects with id, name (the name as you typed it) and enabled.
Refusals: 403 permission_denied or 403 entitlement_required.
GET /symbols The symbols a bot can trade
The underlying symbols a bot may be created for, as plain strings. An index is written with a leading $ and a trailing .X, for example $SPX.X. Use one of these as the bot's symbol setting. Operation id: listSymbols.
Permission: bots.view.
What it returns: symbols, a list of strings.
Refusals: 403 permission_denied or 403 entitlement_required.
Account-wide performance
GET /performance Profit and loss across your accounts
The dashboard's bot profit card as numbers: every open bot trade plus the bot trades opened or closed in the current session, for all the accounts the client may use or for one of them. Live bot profit and loss is before commissions. This is the heaviest bot read, so it counts toward the bot reads limit as well as the reads limit. Operation id: getAccountPerformance.
Permission: bots.performance.view (View performance, trades and logs).
| Name | Type | Required | Meaning |
|---|---|---|---|
account (query) | string | No | One account number instead of all. An account the client may not use, or one that is closed, is not an error: the answer has an empty accounts list, all and today null, and quotesComplete true. |
What it returns:
| Field | Meaning |
|---|---|
accounts | The account numbers the totals cover. |
all | Totals over every open bot trade and the session's trades: currentProfit (realized P/L of closed orders plus open legs valued at current quotes), openCredit (the credit taken in when the trades opened), currentTheta (the market value of the open option legs, short legs positive, long legs negative: the premium still to decay), maxProfit and maxLoss (the summed best and worst P/L each trade has seen so far, never below and never above its current P/L; these are peaks, not the most the trades could make or lose), and strategies, the same currentProfit and currentTheta split by stratCode. |
today | The same shape for the trades opened in the current session. |
quotesComplete | False while a quote for an open position is missing; all and today are then null. Ask again shortly. |
{
"accounts": ["12344821"],
"all": {"currentProfit": "412.50", "openCredit": "1860.00", "currentTheta": "640.00",
"maxProfit": "530.00", "maxLoss": "-210.00",
"strategies": [{"stratCode": "SPXPCS", "currentProfit": "412.50", "currentTheta": "640.00"}]},
"today": {"currentProfit": "95.00", "openCredit": "620.00", "currentTheta": "525.00",
"maxProfit": "140.00", "maxLoss": "-35.00", "strategies": []},
"quotesComplete": true
}
Refusals: 403 constraint_violation when the client is limited to the bots it created, because account totals include other bots; 400 bad_request when account is sent as a list (account[]=); when it is repeated, only the last value is used; 429 rate_limited when the bot reads limit is reached; 503 service_unavailable when the totals could not be read, which is temporary.
The trading pause switch
Pausing trading is an account-wide stop: no bot opens a new trade while the account is paused. Bots keep their own on or off setting, so a resume puts everything back as it was. The trading engine can take a short time to see a pause, an order already at the broker may still fill, and open trades keep being managed until they close.
GET /trading/status Whether trading is paused
The account-wide switch, read fresh. Operation id: getTradingStatus.
Permission: bots.view.
What it returns: paused (true while automated trading is paused), webhooksEnabled (whether webhook launches are on), resumeAt (when a timed pause ends, in UTC, or null), halted (true while tradeSteward has halted the account) and pauseLocked (true while an emergency pause, a sitewide pause or a halt holds the pause, so a resume through the API would be refused).
{"paused": true, "webhooksEnabled": false, "resumeAt": null, "halted": false, "pauseLocked": false}
Refusals: 403 permission_denied; 503 service_unavailable when the state could not be read.
POST /trading/pause Pause trading for the whole account
A stop action. It takes no body and no Idempotency-Key, and every call runs. It sets the account to paused, indefinitely, webhooks included, and clears any launch that was waiting to fire. It never shortens or weakens a pause that is already in force. A client limited to some accounts, or to its own bots, may still pause: the action fails in the safe direction. Stop actions have their own limit, separate from the limits on reads and changes, and they stay available while changes are switched off, so a program that has used up its other limits can still stop trading. Over REST a pause never waits for approval, whatever the client's approval settings; only a pause that reaches tradeSteward through the MCP connector waits for your approval (see Approving Requests That Start Trading). Operation id: pauseTrading.
Permission: trading.pause (Pause all trading). Any Trader plan.
What it returns: paused, always true, and alreadyPaused, true when trading was already paused when the call arrived (the call still clears waiting launches unless an emergency pause is in force, and nothing else changes).
{"paused": true, "alreadyPaused": false}
Refusals: 403 permission_denied; 403 entitlement_required on a plan without API access to bots; 503 lock_busy with Retry-After: 2 when the account's trading settings are being changed by another request (send it again); 503 service_unavailable when the pause could not be confirmed (send it again, or pause on the tradeSteward website).
POST /trading/resume Resume trading after a pause
Sets the whole account back to running. Because it starts trading, it is one of the requests that waits for your approval by default: the answer is 202 with status pending_confirmation, an actionId and expiresAt, nothing has changed yet, and your program reads the outcome from GET /pending-actions/{actionId}. You approve or reject the request on the tradeSteward website, or in the tradeSteward app where app approval is available. A request you do not answer expires after 1 hour. The whole flow, its limits and the cooldown after a rejection are in Approving Requests That Start Trading. When you have turned approval off for the client, the resume runs at once and answers 200. It takes no body. Operation id: resumeTrading.
Permission: trading.resume (Resume all trading). Off by default, any Trader plan; see Safeguards. A client limited to some broker accounts cannot resume, because a resume affects every account.
| Name | Type | Required | Meaning |
|---|---|---|---|
Idempotency-Key (header) | string | Yes | A unique value you choose: 1 to 128 letters, digits, dots, underscores or hyphens. The same key with the same request returns the first answer and never resumes trading a second time. Kept for 2 days. |
What it returns: on 200, resumed (true) and alreadyRunning (true when trading was already running and nothing changed; an account that is already running is answered at once and never waits for approval). On 202, the pending answer described above.
{"status": "pending_confirmation", "actionId": "a_3f9c1b7e2a4d6058c1e7b2", "expiresAt": "2026-11-03T15:12:40Z"}
Refusals: 403 permission_denied; 403 entitlement_required on a plan without API access to bots; 428 idempotency_key_required without the header; 409 idempotency_conflict when the key was used with a different request; 403 constraint_violation for a client limited to some accounts; 409 pause_locked while an emergency pause, a halt or a sitewide pause is active (check pauseLocked first; nothing waits for approval); 409 pending_action_exists with the waiting request's actionId in error.data while a resume request is already waiting; 429 rate_limited when too many requests are waiting for approval, when you rejected one like it a short time ago (error.data.reason says which), or when the limit on turn-on, resume and live edit requests is reached; 503 writes_disabled while changes through the API are switched off (unlike a pause, a resume is a change); 503 lock_busy with Retry-After: 2; 503 service_unavailable with Retry-After: 30 when the rate limiter cannot count the request, or without it when the resume could not be confirmed: read GET /trading/status before sending it again with a new key.
Schema endpoints
Bot settings travel as one flat JSON object of named settings, the same names the bot form saves. Nothing in that object is self-describing, so the API publishes a dictionary that tells your program what each setting is, what values it takes and when it applies, and a second document that explains response fields whose meaning is not obvious from their names. Read both once, cache them, and read the dictionary again when dictionaryVersion changes.
GET /schema/bot-settings The settings dictionary
Every bot and Backtick position setting, one entry per setting. Read it before writing settings: POST /bots, PATCH /bots/{botId} and POST /bots/validate refuse any key the dictionary does not list, and any value outside what it allows. Operation id: getBotSettingsSchema.
Permission: bots.view (View bots) or backtick.view (View Backticks and results); either one is enough.
What it returns: version, a short string that changes whenever the dictionary changes (the same value as dictionaryVersion on GET /me and GET /bots/{botId}), and fields, a list of settings. Each field has:
| Field | Meaning |
|---|---|
key | The name you use in a settings object, for example stop-type or opt1-strikeTarget. |
label | The setting's name on the bot form, for example Stop Type. |
type | The kind of value. Numbers: tinyint, smallint, mediumint, int and decimal. Text: char, varchar, text, mediumtext, string and enum. toggle is an on or off switch that takes 0 or 1 (a JSON boolean is accepted on a write and turned into 1 or 0). json is a structured value; the one such setting is stop-adjustments, a list of rows each with dte, time and setting. |
enum | The allowed values when the setting takes a fixed set, else null. A setting whose choices include the text "null" (for example stop-type, where it means no stop) takes that text or a JSON null. |
valueLabels | What each allowed value means on the bot form, for example {"Q": "Quantity", "P": "Percent", "L": "Leverage", "D": "Dollar Target"} for botsize-type. Null when the values speak for themselves. |
units | The unit of a number, in words, such as HH:MM:SS or points of strike price. Null when there is none. |
scale | For a decimal, how many decimals the setting keeps. A write refuses a number with more decimals than that, a number written with an exponent, or one with more than 15 significant digits. |
appliesTo | ["bots", "positions"] for a setting both a bot and a Backtick position have, ["bots"] for a bot-only setting (such as botName, notes and webhookEnabled), ["positions"] for a position-only setting (slippageDelayOverride), which a bot write refuses. |
appliesWhen | When the setting is read, in words, for example opt1-autoConflict, opt2-autoConflict, opt3-autoConflict or opt4-autoConflict is 1. Null when it always applies. A setting that does not apply can still be sent; it is stored and ignored. |
description | What the setting does, how it relates to other settings and any rule a write enforces. |
column or derivedFrom | Where the value lives. Most settings name their stored column. About a quarter of them are derived from one stored value, and name it in derivedFrom: enterByTimeOrTrigger and multiEntryLength, for example. Your program never needs either; they are there so the dictionary can be traced to the stored bot. |
{
"version": "e3ebc41ac673",
"fields": [
{"key": "botsize-type", "column": "botsize-type", "label": "Position Sizing Type", "type": "char",
"enum": ["Q", "P", "L", "D"],
"valueLabels": {"Q": "Quantity", "P": "Percent", "L": "Leverage", "D": "Dollar Target"},
"units": null, "appliesTo": ["bots", "positions"],
"description": "Position Sizing on the bot form: Q a fixed quantity (botsize-Qty), P a percent of the account's net liquidation value (botsize-percentNLV), L leverage (botsize-leverage), D a dollar target (botsize-dollarTarget). Percent and leverage sizes round down to whole units.",
"appliesWhen": "The bot is a Multi-Entry parent, enterByTimeOrTrigger is time, or botsize-preset is 1."},
{"key": "autoConflict-to", "column": "autoConflict-to", "label": "Auto Conflict To", "type": "decimal",
"enum": null, "valueLabels": null, "units": "points of strike price, 10 means strikes up to 10 points away",
"appliesTo": ["bots", "positions"], "scale": 2,
"description": "Resolve Conflicts, Towards Underlying Mark on the bot form: ...",
"appliesWhen": "opt1-autoConflict, opt2-autoConflict, opt3-autoConflict or opt4-autoConflict is 1."}
]
}
How to use it to build settings:
- Keep the fields whose
appliesToincludesbots. Index them bykey. - Start from a bot you already have:
GET /bots/{botId}returns itssettingsin exactly this shape, and a read sent back unchanged is always accepted. Change the keys you need and send the result toPOST /bots/validate. - For a value with an
enum, send one of the listed values, as the listed type: the values of acharorvarcharsetting are text, so"Q"and neverQunquoted. Avalidation_failedanswer marks such a mistake with the reasoninvalid_type. - Follow the pairings the descriptions name. Changing a type setting (
stop-type,exit-profitTargetType,botsize-type) needs the new type's value setting in the same request, because the old number is never converted. - A time is
HH:MM:SSUS Eastern. The entry window isentryHour-start,entryMinute-start,entrySecond-startand the matching-endtrio; the entry days areentryDOW-M,entryDOW-T,entryDOW-W,entryDOW-RandentryDOW-F.
The next two articles show a full create request built this way. The website's setting-by-setting reference starts at The Edit Bot Form: Every Section at a Glance; the dictionary's label is the name you will find there.
Refusals: 403 permission_denied when the client holds neither permission; 503 service_unavailable when the dictionary could not be read.
GET /schema/response-fields Notes on response fields
Plain-English notes on the response fields whose meaning is not obvious from the name, grouped by the response they appear in. Response bodies never repeat this text, so a program that shows API data to people, or an assistant that reads it, gets the meaning from here once. Operation id: getResponseFields.
Permission: bots.view (View bots) or backtick.view (View Backticks and results); either one is enough.
What it returns: surfaces, an object keyed by response surface (me, bots, trades, performance, balances, preview, logs, fills, tradingStatus, history, botWrites, pendingActions, and the Backtick surfaces). Each surface is a map from a field path to its note. A dotted path names a nested field, for example all.currentTheta under performance.
{
"surfaces": {
"tradingStatus": {
"paused": "True while automated trading is paused for the account, the same field get_account_context returns under trading.",
"pauseLocked": "True while an emergency pause, a sitewide pause or a halt holds the pause.",
"resumeAt": "When a timed pause ends, in UTC; null when none is set."
},
"history": {
"actor.source": "website for a change made on the website (or by tradeSteward), api for a change made through the API.",
"actor.thisConnection": "True when this connection made the change. Another connection is never identified."
}
}
}
Refusals: 403 permission_denied when the client holds neither permission.
Trade log and strategies
GET /trade-log Read broker orders
Requires trades.view and a Trader subscription (subscriptionType 3 or higher). This permission starts on for new MCP connections and API clients when the plan allows it. Returns trades and nextCursor. Each order has its serial, account, order number, bot id (null for manual trades), symbol, UTC fill time, strategy code, order type, filled quantity, leg fills, net credit, fees and notes. No raw broker payload is returned.
Filters: accountNum, botId (the string null for manual trades), strategyId (the string null for unassigned trades), symbol, orderNum, and inclusive Eastern dates from and to. Dates default to the last 30 days; an order-number lookup without dates searches all dates. Newest serial first. Set limit from 1 to 100 (default 50), then send nextCursor as cursor with the same filters. Account restrictions apply. A connection limited to its own bots sees only their orders.
POST /trade-log/{serial}/strategy Set a manual order’s strategy
Requires trades.strategy.edit and a Trader subscription. This permission starts on for new MCP connections and API clients when the plan allows it. Send {"strategyId":"MYSTRAT"} to assign one of your enabled strategies, or {"strategyId":null} to clear it. Use the order’s serial from the trade log. Returns serial, accountNum, orderNum and strategy (an object with id, name and enabled, or null).
The website’s processor updates opening/closing matches and strategy P/L during the call. Repeating reprocesses the order. Bot orders and connections restricted to their own bots are refused with 403 constraint_violation; inaccessible orders answer 404 not_found, and an unavailable strategy answers 422 validation_failed. After a processing failure, read the log before retrying because the assignment may already have changed.