Skip to main content

User API: Backtick Results, Charts, Links and Bots

Last updated November 8, 2026

Read Backtick summaries, results, position analytics and charts; create chart, portfolio and trade download links; or save a position as a bot.

See Use the tradeSteward API From Your Own Code for plans. The comparison with live trades also needs bots.performance.view. Every endpoint needs backtick.view. Converting a position also needs backtick.convert, bots.create and a Trader plan. Results require status completed; see Running Backticks and Following a Run.

All reads share a rate limit. Trade-data reads (trades, series, daily, breakdowns, positions-daily, position results, analytics and charts) also have tighter account rate and concurrency limits. Exceeding either returns 429 rate_limited with Retry-After.

How results are shaped

  • Money is a decimal string in dollars, net of commissions (for example "18420.00"). Rates such as winRate and cagr are fractions (0.8156 is 81.56 percent). Dates are YYYY-MM-DD; times on the wire are UTC.
  • A view that has no results to show answers 200, not an error. The trades, series, daily, breakdowns and positions-daily views, a position's results and the chart answer status, results null and a reason sentence (for example "This Backtick has not been run."), and a scheduled Backtick adds positionInLine and scheduledReason. This happens to a completed Backtick too when its results data cannot be read. The summary answers metrics null with metricsUnavailableReason (and positionInLine and reason while scheduled), the positions view answers resultsUnavailableReason with each position's metrics null, and analytics answers analytics null with a reason. Check for those nulls before you parse, not only status.
  • The headline metrics of a whole Backtick are totalProfit, totalReturn, cagr, maxDrawdown, maxDrawdownPct, maxDrawdownDays, sharpe, sortino, mar, ulcer, profitToPremium, totalPremium, totalCommissions, tradeCount, contractCount, dayWinRate, avgWinningDay, avgLosingDay, bestDay, worstDay and avgProfitPerContract. A position's metrics use the same names, with per-trade versions in place of the per-day ones: winRate, avgWinPerContract, avgLossPerContract, maxWinPerContract, maxLossPerContract, plus correlationToRest. GET /schema/response-fields explains each one.
  • Every view is sized to be read as text, and you can ask for more where it says so. Send Accept-Encoding: gzip (every common HTTP client does; curl needs --compressed) for the large views.
  • On a completed SweepTick, positions are alternatives to compare, not parts of one portfolio, so the series and breakdowns views and the chart need positionIds; without it they answer 400 bad_request.

Results views

GET /backtests/{uid}/results The summary

Returns the summary of a completed Backtick: the same answer as the summary view, for the whole Backtick, with no position filter. Operation id: getBacktestResults.

NameTypeRequiredMeaning
uidpath, stringYesThe Backtick id, from GET /backtests.

Returns the Backtick summary as GET /backtests lists it: backtestUID, name, status, libraryStatus, source, expiresAt, testType, testPeriodStart, testPeriodEnd, startingBalance, createdAt, completedAt, metrics (the headline metrics above; null with metricsUnavailableReason when the Backtick has not completed, and always null for a SweepTick, whose positions are alternatives: read each position's metrics with the positions view) and errorMessage.

{"ok": true, "data": {
  "backtestUID": "a1B2c3D4e5F6", "name": "SPX 10 Delta Put Spread 9:31a", "status": "completed",
  "testPeriodStart": "2024-01-02", "testPeriodEnd": "2025-12-31", "startingBalance": "100000.00",
  "completedAt": "2026-11-03T14:20:51Z",
  "metrics": {"totalProfit": "18420.00", "totalReturn": 0.1842, "cagr": 0.0884, "maxDrawdown": "4210.00",
              "maxDrawdownPct": 0.0398, "sharpe": 1.62, "tradeCount": 488, "dayWinRate": 0.8156, "...": "..."},
  "metricsUnavailableReason": null, "errorMessage": null
}, "error": null, "apiVersion": "rest-v1", "requestId": "req_1cR4"}

Refusals: 404 not_found.

GET /backtests/{uid}/results/{view} One part of the results

Returns one part of a completed Backtick's results. Operation id: getBacktestResultsView.

NameTypeRequiredMeaning
uidpath, stringYesThe Backtick id.
viewpath, stringYessummary, positions, positions-daily, trades, series, daily, breakdowns or grid (a SweepTick only). Any other value answers 404 not_found.
positionIdsquery, stringNoComma-separated position ids, 300 at most, to read those positions only. Leave it out for every position. Not used by summary. Required for series, daily and breakdowns on a completed SweepTick.
limitquery, integerNoRows per page. trades: 50 by default, 500 at most, or 100 when detail=legs sends the legs. daily: 500 by default, 3000 at most. positions and positions-daily: 25 by default, 50 at most. A larger number is treated as the view's most.
cursorquery, stringNotrades, daily, positions and positions-daily: nextCursor from the previous page.
detailquery, stringNotrades only: legs adds each trade's legs, levels, fees and settlement check (below). Any other value answers 400 bad_request.
fieldsquery, stringNotrades only: comma-separated item members to keep, such as pl,grossPl,settlementCheck. positionId and tradeNo always stay. An unknown member, or one only detail=legs adds named without it, answers 400 bad_request.
maxPointsquery, integerNoseries only: the most points to return, 3 to 500, default 60. A larger number is treated as 500 and a smaller one as 3.
metric, xAxis, yAxis, hold, top, rankBy, orderqueryNogrid only: the position metric the cells show (totalProfit by default), the axis indexes across and up, hold as axis:valueIndex pairs such as 2:4,3:0 for unplotted axes of a full grid, how many best positions to list (10 by default, 50 at most), and the metric and direction they rank by. See the OpenAPI document for every value.

What each view returns:

viewReturns
summaryThe same as GET /backtests/{uid}/results, with assumptions for a completed Backtick (null otherwise): basis (net), commission (the profile, its rates in dollars, perLegRates per symbol and editedSinceRun), fills (coercePrice, the slippage and delay overrides and positionsWithOverrides), dataThrough, skippedDays (from, to, reason skipDays, positionSkipDays, marketClosed or afterDataThrough, the holiday name, and the positionIds that skip a day the Backtick itself does not; null with skippedDaysUnavailableReason when processing exceeds 10,000 input ranges or range/position pairs across the Backtick and its positions), defaultsApplied and lateExits (count and lastExitDate of trades that exited after the test period, and stillOpen, trades with no exit date; when the trade data cannot be read just then, those three are null with unavailableReason, and the summary still answers 200).
positionsstatus, resultsUnavailableReason, positions (each with positionId, name, botId, symbol and the full position metrics), totalPositions and nextCursor. 25 to a page, 50 at most. Works before a run too, with metrics null. A completed Backtick's page carries the same assumptions as the summary.
positions-dailyReturns all selected positions, or pages when limit or cursor is sent (25 by default, 50 maximum): status, startingBalance, dictionaryVersion, totalPositions, dates, commonSettings and positions. dates lists exit days once, oldest first. Each position has positionId, name, botId, symbol, metrics, differing settings (overlay onto commonSettings), Multi-Entry entries or parentPositionId, and daily: decimal P/L strings aligned to dates, null without exits. Pages share the selection's full dates, commonSettings and totalPositions; follow nextCursor until null. Size grows with positions times exit days; use gzip or pagination for comparisons and equity curves.
tradesstatus, items (each with positionId, tradeNo, entryDate, entryTime, exitDate, exitTime, pl, quantity, exitReason (cut to 60 characters), entryPrice, exitPrice) and nextCursor. 50 to a page, up to 500 with limit (100 when detail=legs sends the legs). With detail=legs each item adds expirationType, underlyingEntry, underlyingExit, underlyingSettlement, vixEntry, fees (entry, exit, assignment, total), grossPl (pl plus the fees), legs and settlementCheck. A leg has legNo, legType, strike, expiration, expirationType, quantity (the leg's contracts), entryBid, entryAsk, entryPrice, exitBid, exitAsk, exitPrice, exitDate, exitTime, exitReason, underlyingSettlement and pl; its entryPrice and exitPrice are signed per share, a credit positive and a debit negative, and its bid and ask are the unsigned quotes. For a trade whose every leg ended at expiration, settlementCheck recomputes the position's value at settlement from each strike and that leg's settlement print (the AM print for an AM-settled expiration such as the SPX monthly, the PM print otherwise): intrinsicAtSettlement, expectedGrossPl and settlementDelta (grossPl minus expectedGrossPl, 0.00 when they agree, a few cents off on a large trade from the run's 32-bit storage); it is null for a trade that ended another way. A member an older run did not record is null. The cursor belongs to one run: if the Backtick is run again, a cursor from the old run answers 400 bad_request and you start again without one.
seriesstatus, startingBalance, points (each with date, equity, drawdown and drawdownPct) and totalPoints. 60 points unless maxPoints asks for more (500 at most). A longer series is thinned evenly; the first point, the last point and the deepest drawdown are always kept, and so is the first point with equity below zero when maxPoints is 4 or more.
dailystatus, startingBalance, stillOpenPl (the marked P/L of trades still open when the data ended, which is in the totals but on no day), totalRows, rows (each with date, pl, equity, drawdown and drawdownPct) and nextCursor. Every exit day, oldest first and not thinned, with equity and drawdown over the whole test; days with no exits are not rows. 500 days to a page, up to 3000 with limit.
breakdownsstatus, monthly (month as YYYY-MM, pl), yearly (year as a string, pl), dayOfWeek (day from Monday to Friday by entry day, pl, trades; null when the run recorded no entry dates) and exitMix (reason, trades, pl; null when the run recorded no exit reasons, and a blank reason is unspecified): the 15 most common exit reasons, with the rest summed into one other row.
gridA completed SweepTick in one call: axes, xAxis, yAxis, held, grid (values[j][i] and positionIds[j][i] at x value i and y value j, null where nothing was tested, and counts for a sampled design), best, core, top and assumptions. grid is null with gridUnavailableReason when the axes cannot be read or a sample's plotted axes have more than 4096 value pairs.
GET /rest/v1/backtests/a1B2c3D4e5F6/results/series?maxPoints=4

{"ok": true, "data": {
  "status": "completed", "startingBalance": "100000.00",
  "points": [
    {"date": "2024-01-02", "equity": "100000.00", "drawdown": "0.00", "drawdownPct": 0},
    {"date": "2024-08-05", "equity": "104120.00", "drawdown": "4210.00", "drawdownPct": 0.038863},
    {"date": "2025-06-30", "equity": "113880.00", "drawdown": "0.00", "drawdownPct": 0},
    {"date": "2025-12-31", "equity": "118420.00", "drawdown": "0.00", "drawdownPct": 0}
  ],
  "totalPoints": 501
}, "error": null, "apiVersion": "rest-v1", "requestId": "req_8vL2"}

Refusals: 400 bad_request when positionIds is not a comma-separated list of up to 300 positive ids, when limit or maxPoints is not a whole number, for a malformed cursor or one from an earlier run, for series and breakdowns on a SweepTick without positionIds, or for a grid member on another view, paging on grid, or an axis or hold the SweepTick does not have; 404 not_found for the Backtick or for a position id that is not in it; 429 rate_limited; 503 service_unavailable when results are temporarily unavailable.

GET /backtests/{uid}/positions/{positionId}/results One position's results

Returns one position's headline numbers, equity series and first page of trades, or with view=analytics its deeper statistics. Operation id: getBacktestPositionResults. Counts toward the trade-data limit.

NameTypeRequiredMeaning
uidpath, stringYesThe Backtick id.
positionIdpath, integerYesThe position id, from GET /backtests/{uid}.
viewquery, stringNoanalytics returns the position's analytics instead of the headline, series and trades. It is the only value.
maxPointsquery, integerNoThe most points of the equity series, 3 to 500, default 60. A larger number is treated as 500 and a smaller one as 3. Not used with view=analytics.

Without view, returns positionId, status, headline (tradeCount, totalProfit, winRate, avgWin, avgLoss, maxDrawdown), reason (a sentence when the position made no trades, otherwise null), series and totalPoints as in the series view, trades (the first 50) and tradesNextCursor. Read further pages with GET /backtests/{uid}/results/trades?positionIds={positionId}&cursor=....

With view=analytics, returns positionId, status, view analytics, reason and analytics: confidence (tradeCount, bootstrapped and bootstrapIterations; sharpePerTrade, sortinoPerTrade, marByTrade, expectancy, profitFactor, winRate, maxDrawdownByTrade, averageDrawdownByTrade, valueAtRisk5 and expectedShortfall5, each with value, low and high; and recoveryFactor, probabilitySharpePositive, probabilisticSharpeRatio, skewness, excessKurtosis, tradeAutocorrelation and splitSample with earlySharpe, lateSharpe and delta), drawdowns (each with start, trough and recovery dates, depth, depth percent, recovery days and the trades that drove it), bestDays and worstDays (each day with its P/L, its real tradeCount and its first 10 trades), streaks (longestWin, longestLoss, current, bestPeriod and worstPeriod, each null when there was none, and periodDays) and fillQuality (entry and exit, each with average, p90, p95, p99, max and observations, or null for a side with nothing observed; the whole object is null with fillQualityUnavailableReason when the run did not record spreads). analytics is null with a reason when the position made no trades.

Refusals: 400 bad_request for any view other than analytics or a maxPoints that is not a whole number; 404 not_found; 429 rate_limited; 503 service_unavailable.

Comparing a Backtick with live trades

GET /backtests/{uid}/live-comparison A Backtick against your live bots

Pairs each trade of a completed Backtick with the live trade it matches and returns the counts, the timing and credit gaps, the profit and loss of both sides and a page of the trades. Operation id: getBacktestLiveComparison. Permissions: backtick.view (View Backticks and results) and bots.performance.view (View performance, trades and logs), both required. It counts toward the bot read limit and the trade-data limit.

NameTypeRequiredMeaning
uidpath, stringYesThe Backtick id. It must be your own and not in the trash.
botIdsquery, stringNoComma-separated bot ids, 20 at most. A Multi-Entry bot stands for all its entries. Leave it out for the bots the Backtick was made from. A bot you may not see is 404 not_found.
positionIdsquery, stringNoComma-separated position ids of the Backtick, 300 at most. Required on a completed SweepTick.
from, toquery, dateNoThe window, YYYY-MM-DD, US Eastern days, both ends included. Send both or neither. Default: the Backtick's test period.
excludeOneOff, excludePartial, excludeNormalquery, booleanNoLeave out live trades opened by a one-off launch, that completed a partial fill, or that opened on the bot's schedule. All default to false.
matchquery, stringNoall (default), matched, liveOnly or backtickOnly. It filters the trades list only; the totals always cover everything in scope.
limitquery, integerNoTrades per page, 1 to 200, default 50.
cursorquery, stringNonextCursor from the previous page, unchanged.

With none of botIds, positionIds, from, to or the exclusions, and a saved comparison on the Backtick from the website, the comparison uses that report's bots, window and options. Otherwise a Backtick made from bots compares the bots it was made from over its test period.

A Backtick that is not completed answers 200 with backtestUID, backtestName, status and a reason, and nothing else. A completed Backtick answers scope (where the bots and window came from), bots, counts, timing, credit, pl, assumptions, trades, nextCursor and totalTrades. Money is a decimal string in dollars. Credits are per contract in option price units, credit positive. Every difference is live minus Backtick. GET /schema/response-fields describes each member.

Refusals: 400 bad_request for a bad date, window, limit, match, list or cursor, or a cursor from an earlier state of the comparison (start again without one); 403 permission_denied; 404 not_found for a Backtick, bot or position that is not yours or not there; 422 validation_failed when no bot can be compared or the window holds more trades than can be paired (narrow the window or the bots); 429 rate_limited; 503 service_unavailable.

curl -H "Authorization: Bearer $TOKEN" --compressed \
  "https://api.tradesteward.com/rest/v1/backtests/a1B2c3D4e5F6/live-comparison?botIds=bOt0000001&from=2026-09-01&to=2026-10-31&match=matched&limit=20"

GET /versus Compare two sides

Compares two sides over the entry days both cover, US Eastern. Each side is a position of a completed Backtick or one of your bots, so it compares a Backtick with a bot, two Backticks or two bots. Operation id: compareVersus. Permission: backtick.view (View Backticks and results) for a Backtick side and bots.performance.view for a bot side. A Backtick side counts toward the trade-data limit.

NameTypeRequiredMeaning
aBacktestUID, aPositionIdquery, string and integerOne side formSide a as a Backtick position: the Backtick id and the position id, from GET /backtests/{uid}.
aBotIdquery, stringOne side formSide a as a bot, in place of the two above.
bBacktestUID, bPositionId, bBotIdqueryOne side formSide b, the same way.
fromquery, dateNoThe first entry day to include, YYYY-MM-DD. Defaults to the first day both sides cover.
toquery, dateNoThe last entry day to include, YYYY-MM-DD. Defaults to the last day both sides cover.

Trades are paired within each entry day, in entry order. Returns window (the days compared, with each side's own span), a and b (each side as compared, with its name; a Multi-Entry bot or position stands for its entries), sides (each side's trades, contracts, net and gross P/L, max drawdown, first and last entry day), matching (how many trades were paired, the shares paired and how far apart the paired entries were), strikes (how often the paired trades held the same legs), credit (the mean difference in entry price per unit, and its sign), correlation (per trade and per day), gaps (side a minus side b) and basis, a sentence on how the sides were measured. P/L is net of commissions on both sides, so a bot's totals here differ from GET /bots/{botId}/performance. A bot side leaves out open trades and Launch Now and webhook trades. Money is a decimal string; rates are fractions.

Refusals: 400 bad_request for a missing or mixed side, the same side twice, sides that share trades, a bad date, sides that do not overlap in time, a window with no days left, or a side with more than 3000 trades in the window; 404 not_found for a Backtick, position or bot you cannot see; 409 invalid_state for a Backtick that has not completed or a side with no trades; 429 rate_limited; 503 service_unavailable. When one side is at fault, error.data.side names it.

Charts and chart links

GET /backtests/{uid}/results/chart The equity chart as an SVG

Returns a chart of a completed Backtick's equity line and its drawdown below the running peak, drawn from the same days as the series view (up to 500 of them), as image/svg+xml. It is text: a program can save it and a browser can show it. Keep it out of an AI prompt, and give a person a chart link instead. Operation id: getBacktestResultsChart. Counts toward the trade-data limit.

NameTypeRequiredMeaning
uidpath, stringYesThe Backtick id.
positionIdsquery, stringNoComma-separated position ids, 300 at most, to chart those positions together. Leave it out for the whole Backtick. Required on a completed SweepTick.

Returns 200 with the SVG body. A Backtick with no results answers the usual JSON envelope with results null and a reason, never an empty picture, so check the Content-Type before you save. Refusals: 400 bad_request; 403 permission_denied; 404 not_found; 409 invalid_state when the Backtick was run again while the chart was drawn (ask again); 429 rate_limited; 503 service_unavailable with Retry-After when results are temporarily unavailable.

curl -H "Authorization: Bearer $TOKEN" --compressed \
  "https://api.tradesteward.com/rest/v1/backtests/a1B2c3D4e5F6/results/chart" -o equity.svg

Creates a one-time browser link to a completed Backtick's chart without changing the Backtick. The page's button draws and shows the chart once; opening the page alone, including link previews and mail scanners, uses nothing. Operation id: createBacktestChartLink. Optional body; no Idempotency-Key.

NameTypeRequiredMeaning
uidpath, stringYesThe Backtick id.
positionIdsarray of integersNo1 to 300 position ids to chart together. Leave it out for the whole Backtick. Required for SweepTick chart; omit for grid.
ttlSecondsintegerNoHow long the link stays valid: 60 to 3600 seconds. Default 900 (15 minutes).
viewstringNochart (the default) or grid: a heat map of a completed SweepTick's grid, which takes metric, xAxis, yAxis, hold (a list of {axis, valueIndex} objects), top, rankBy and order as the grid view does, and no positionIds.

Returns 201 with linkId (for revoking; it carries no secret), url (give it to the person exactly as it is; this is the only time its secret is shown), expiresAt (UTC), expiresAtEastern (the same moment in US Eastern time, for telling a person), backtestUID, positionIds (or null for the whole Backtick or a heat map) and view (chart or grid).

{"ok": true, "data": {
  "linkId": "k_3f9a1c77be0d4e21a6c5b8",
  "url": "https://api.tradesteward.com/view#t=...",
  "expiresAt": "2026-11-03T14:50:00Z",
  "expiresAtEastern": "Nov 3, 2026 9:50 AM EST",
  "backtestUID": "a1B2c3D4e5F6", "positionIds": null, "view": "chart"
}, "error": null, "apiVersion": "rest-v1", "requestId": "req_2hW7"}

The link expires with its creating access token (tokens last an hour), or immediately on revocation, client revocation, loss of backtick.view or a new Backtick run. expiresAt gives its actual expiry. Limits: 20 unopened links per account, 5 per client, plus a creation rate limit.

Refusals: 400 bad_request for unknown fields, over 300 positionIds, ttlSeconds outside 60 to 3600, a SweepTick chart without positionIds, grid members on chart, positionIds on grid, grid on a non-SweepTick, or invalid axes/holds; 409 invalid_state for incomplete runs, unreadable heat-map axes, excessive sampled value pairs, or tokens with under a minute left (refresh first); 429 rate_limited for rate or unopened-link limits; 404 not_found; 503 service_unavailable, including while heat-map links are disabled.

Revokes a chart link this client made. A link that has not been opened becomes revoked and shows nothing, even if its page is being opened right now. Takes no body. Operation id: revokeChartLink.

NameTypeRequiredMeaning
linkIdpath, stringYesThe linkId the mint returned (the form k_ and 22 hex characters). It is not the secret in the url.

Returns 200 with linkId and status: revoked (it is ended now, or was already revoked), used (it had already been opened) or expired (it was already past its time). Refusals: 404 not_found for an unknown id or a link of another client; 503 service_unavailable while chart links are switched off.

Creates a durable portfolio link from cells of one completed Backtick or SweepTick. Each cell is a position with its own entry time and swept settings. Signed-in visitors can confirm a Test as a Backtick or create disabled bots in their own accounts. Your Backtick stays unchanged. Operation id: createBacktestPortfolioLink. Counts as a change and stops while changes are disabled. No Idempotency-Key; repeating creates another link.

NameTypeRequiredMeaning
uidpath, stringYesThe Backtick id.
positionIdsarray of integersYesThe positions that are the cells, 1 to 300 cells after Multi-Entry positions are expanded to their entries.
namestringNoA name shown on the page, at most 30 characters. Default Portfolio. Double quotes, backslashes, line breaks, tabs and other control characters, a < that starts a tag, and {word} placeholders are refused.
capitalPerContractintegerNoThe capital per contract the page starts from, in whole dollars, 1000 to 1000000. Default 40000.
lifetimeDaysintegerNoHow many days the link works, 1 to 365. Default 90.
allocationobjectNoOne split of contracts over the cells, to also get testUrl and botsUrl. contracts (required) is the contracts of cell 0, 1, 2 and so on in the order of cells; a list may stop early and the rest take none; each is at most that cell's maxContracts, and all together 1 to 500. capitalPerContract here applies to those two links only. With an allocation, send the positions of the entries, never a Multi-Entry position itself.

Returns 201 with linkId, url (shared page, no secret), fragment (p= plus the secret, shown once), expiresAtEastern (US Eastern), cells (index, positionId, Eastern entryStart, variant, maxContracts), and allocation testUrl/botsUrl. variant is an opaque 40-hex key, equal for cells differing only in entry time; compare, never parse. maxContracts: 49 for $SPX.X (fewer with leg ratios), 100 otherwise. Give the person url + # + fragment. Give testUrl/botsUrl unchanged. Per visitor and link: 5 Tests and 3 Create bots actions daily, 60 previews hourly.

POST /rest/v1/backtests/a1B2c3D4e5F6/portfolio-links
{"positionIds": [48213, 48214], "name": "10d PCS ladder", "lifetimeDays": 30,
 "allocation": {"contracts": [2, 1]}}

{"ok": true, "data": {
  "linkId": "p_91c0ad4e7b3f4a6d8e2c15",
  "url": "https://www.tradesteward.com/backtick/portfolio-link",
  "fragment": "p=....",
  "expiresAtEastern": "Dec 3, 2026 9:12 AM EST",
  "cells": [
    {"index": 0, "positionId": 48213, "entryStart": "09:31:00", "variant": "5b0e3c9d2a7f41e8b6c0d9a3f2e1b4c7d8a9e0f1", "maxContracts": 49},
    {"index": 1, "positionId": 48214, "entryStart": "10:00:00", "variant": "5b0e3c9d2a7f41e8b6c0d9a3f2e1b4c7d8a9e0f1", "maxContracts": 49}
  ],
  "testUrl": "https://www.tradesteward.com/backtick/portfolio-link#p=...&q=2,1&c=40000&do=test",
  "botsUrl": "https://www.tradesteward.com/backtick/portfolio-link#p=...&q=2,1&c=40000&do=bots"
}, "error": null, "apiVersion": "rest-v1", "requestId": "req_6pD3"}

A link lasts 90 days unless lifetimeDays says otherwise, stays valid after the client that made it is removed, ends at once when revoked or when the account is closed, and does not work while the account is inactive or has API access turned off. An account can hold 50 live links and a client 20, and making links is rate limited too.

Refusals: 422 validation_failed includes fieldErrors[0].reason and, when applicable, fieldErrors[0].positionId: unknown_position (outside this Backtick), share_prohibited (group sharing restriction), signals (Backtick/position signals file), trigger_entry, endpoint_filter, position_reference (another position/bot), skip_days (different position skip days), max_positions (Multi-Entry open-position limit), entry_before_open, not_per_contract (enable qty1normalize or use fixed quantity), settings_invalid (nested fieldErrors), duplicate_cell, too_many_cells, container_in_allocation (Multi-Entry container with allocation). For allocation.contracts: not_a_list, too_many_entries, not_a_count, over_cell_cap, total_out_of_range. Unknown body members produce reason unknown. Other refusals: 409 invalid_state (incomplete or concurrently rerun Backtick); 429 rate_limited (minute/client/account cap); 403 permission_denied (lost backtick.view during creation); 404 not_found (outside your account); 503 service_unavailable.

Ends a portfolio link of your account. Any client of the account can revoke any of its links, whichever client made it. A live link becomes revoked and shows nothing at once. Bots and Backticks already made from the link stay. Takes no body. It counts as a change and stops while changes are switched off. Operation id: revokePortfolioLink.

NameTypeRequiredMeaning
linkIdpath, stringYesThe linkId the mint returned (the form p_ and 22 hex characters). It is not the secret in the fragment.

Returns 200 with status: revoked (now, or already revoked before) or expired (past its time and never revoked). Refusals: 404 not_found for an unknown id or a link of another account; 503 writes_disabled while changes are switched off.

Exporting trades

POST /backtests/{uid}/exports A file of a Backtick's trades

Exports a completed Backtick's trades with legs through a signed download link, without changing the Backtick. Identical requests on the same run share one export: repeat to poll. Operation id: createBacktestExport. Optional body; no Idempotency-Key.

NameTypeRequiredMeaning
uidpath, stringYesThe Backtick id. It must be your own Backtick, not one shared with you.
formatstringNocsv (the default), the same file as the website's trade export, or json.
positionIdsarray of integersNo1 to 300 position ids to export. Leave it out for the whole Backtick. A repeated id counts once.
excludeUnopenedbooleanNoLeave out the trades an entry filter stopped from opening. Default false.
retrybooleanNoQueue the export again: after status failed, or after a ready file whose link answered 302. Send it once, then poll without it. Ignored while queued or running. Default false.

Every answer holds exportId, backtestUID, status, format, positionIds and excludeUnopened, then url, expiresAt, expiresAtEastern, filename, trades and bytes when ready, pollAfterSeconds while prepared, and reason and message when failed; the rest are null.

  • 202 with status queued or running: the file is being prepared. Send the same request again after pollAfterSeconds. It is usually ready within seconds, and can take minutes when the export queue is busy.
  • 200 with status ready: url downloads the file until expiresAt, which is at most an hour away and never later than the end of the newest access token of this connection's sign-in (a refreshed token extends it). Sending the request again gives a fresh link while the file is kept, for 7 days. When that newest token has under a minute left the answer is 409 invalid_state: get a new token and ask again.
  • 200 with status failed: reason is too_large when the export holds more trades than one file may (name fewer positions), otherwise unavailable. Send the request with retry true to queue it again.
{"ok": true, "data": {
  "exportId": "5f0c2b9e41d7a3c86e1f04b2d9a7c315", "backtestUID": "a1B2c3D4e5F6", "status": "ready",
  "format": "csv", "positionIds": null, "excludeUnopened": false,
  "url": "https://cdn.tradesteward.com/exports/...",
  "expiresAt": "2026-11-08T15:20:00Z", "expiresAtEastern": "Nov 8, 2026 10:20 AM EST",
  "filename": "SPX_Put_Spread-trades-20261108-141912.csv", "trades": 488, "bytes": 61234,
  "pollAfterSeconds": null, "reason": null, "message": null
}, "error": null, "apiVersion": "rest-v1", "requestId": "req_2hW7"}

The file is served gzip encoded, typed text/csv for both formats. Download it with a client that decodes gzip and does not follow redirects:

curl --compressed -o trades.csv "$URL"

Leave out -L. A 302 from the link means the file was lost: send the request again once with retry true, then poll without it. A 410 means the link expired: send the request again for a fresh one. CSV text beginning with a spreadsheet formula marker is prefixed with an apostrophe; numeric values retain their signs. A JSON file is one document: its trades each hold their legs, money is a decimal string, a value the run did not record is null, and its last member, rowCount, is the number of trades.

Export links remain valid until expiresAt, including after client revocation. Trashing or purging the Backtick, or closing the account, leaves existing files until their 7-day expiry. API Backticks enter the trash 48 hours after their last change unless saved; trashed Backticks cannot be exported.

Refusals: 400 bad_request for any other body field, a format other than csv or json, an empty or too long positionIds, or a flag that is not true or false; 404 not_found for a Backtick that is not yours, is in the trash, or a position that is not in it; 409 invalid_state when the Backtick has not completed; 422 validation_failed naming positionIds when the whole Backtick has more trades than one export may; 429 rate_limited when too many exports of the account are being prepared or the rate limit is reached; 503 service_unavailable when exports are temporarily unavailable.

From a position to a bot

POST /backtests/{uid}/positions/{positionId}/to-bot Save a position as a new bot

Saves a Backtick position as a new bot on your broker account. The bot is always saved switched off. Enable it on the Bots page, or through code with permission and approval (see Approving Requests That Start Trading). Operation id: convertPositionToBot. Requires backtick.convert, bots.create, backtick.view and the Idempotency-Key header.

NameTypeRequiredMeaning
uidpath, stringYesThe Backtick id. The Backtick may be in any run status except the trash.
positionIdpath, integerYesThe position to convert.
accountNumstringYesThe broker account the bot goes on, from GET /accounts.
stratCodestringYesThe strategy group’s id from GET /strategies.

Returns 201 with the new bot as GET /bots lists it (botId, name, account, strategy, enabled false, version and the rest; see Reading Bots, Trades and Logs), plus convertedFrom with the backtestUID and positionId. The call counts toward the limit on bot creates and edits, and toward the changes limit.

POST /rest/v1/backtests/a1B2c3D4e5F6/positions/48213/to-bot
Idempotency-Key: tobot-48213-1
{"accountNum": "4821", "stratCode": "PCS"}

"data": {"botId": "bQ7xM2kL9p", "botName": "10d PCS 9:31", "enabled": false, "accountNum": "4821", "stratCode": "PCS",
         "version": "1", "...": "...", "convertedFrom": {"backtestUID": "a1B2c3D4e5F6", "positionId": 48213}}

Refusals: 403 permission_denied names missing permissions; without backtick.view, nothing is read or saved. 403 entitlement_required: plan disallows API bot creation. 422 validation_failed: missing accountNum/stratCode, unknown account, unsupported settings or dependencies on other Backtick positions; fieldErrors names failures. not_applicable settings belong to disabled features: edit the position (clone completed Backticks first), then retry. 422 family_edit_unsupported: Multi-Entry conversion unavailable. 409 invalid_state: reason in_trash. Other refusals: 404 not_found; 429 rate_limited; 503 service_unavailable with outcome saved, not_finished or unknown when creation/readback was incomplete. Check your bots before retrying; any created bot is disabled.

  An API client limited to the bots it created can read and change the new bot; one limited to certain broker accounts can only put the bot on one of those accounts. Account limits are set on Connected Apps.