Development Guides

Tax Loss Harvesting (TLH)

Overview

The Tax Loss Harvesting API provides endpoints to identify opportunities, validate trades, and execute tax-loss harvesting transactions. The workflow consists of three main steps:

  1. Identify opportunities (Manual or Automated)
  2. Validate proposed trades
  3. Generate trades

Authentication

All endpoints require authentication. Invalid or missing authentication tokens will result in a 401 Unauthorized response.

Endpoints

Choose between two workflows:

  1. Manual TLH
  2. Automated TLH

1A. Manual TLH

POST /v1/tradetool/taxLossHarvesting/action/createTrade

Identifies tax harvesting opportunities based on specific gain/loss criteria you define

Required Parameters

isFullPositionHarvest (boolean): Whether to harvest full position only

  • false: Allow partial position sales
  • true: Only allow full position sales

ignoreDoNotTLH (boolean): Whether to include positions marked as “Do Not TLH”

  • false: Exclude Do Not TLH positions
  • true: Include Do Not TLH positions

isGainHarvesting (boolean): Whether to look for gains or losses

  • true: Look for gains
  • false: Look for losses

amount (decimal): Maximum gain/loss amount to consider

  • null: No limit
  • amount: Maximum gain/loss amount

term (integer): Holding period filter

  • 1: Both short and long term
  • 2: Short term only
  • 3: Long term only

type (string): Entity type to analyze

  • “Portfolio”
  • “Account”

ids (integer[]): Array of portfolio or account IDs to analyze

Optional Parameters:

securityTypeIds (integer[]): Filter by security types

securityIds (integer[]): Filter by specific securities

minimumGainLoss (integer): Minimum gain/loss threshold type

  1. Per lot
  2. Per holding

minimumGainLossAmount (decimal): Minimum gain/loss amount

minimumGainLossPercent (decimal): Minimum gain/loss percentage

1B) Automated TLH

POST /v1/tradetool/taxLossHarvesting/action/createTLHTrade

Identifies opportunities using preset thresholds. Processes up to 500 portfolios/accounts per batch.

Required Parameters

term (integer): Holding period filter (1, 2, or 3)

type (string): Entity type (“Portfolio” or “account”)

ids (integer[]): Portfolios/accounts to analyze

bucketArray (integer[][]): Max 2 buckets, 250 elements each

batchId (string): Unique identifier (obtain via createTLHTradeBatchId endpoint)

Optional Parameters:

securityTypeIds (integer[]): Filter by security types

securityIds (integer[]): Filter by specific securities

To generate a batch ID:

POST /v1/tradetool/taxLossHarvesting/action/createTLHTradeBatchId

2. Trade Validation

POST /v1/tradetool/taxLossHarvesting/action/validateTLHSecurities

Validates proposed trades for wash sales and other restrictions.

Required Parameters:

allowWashSaleOnSell (boolean): Override wash sale restrictions

isTaxHarvesting (boolean):

  • true: Manual TLH
  • false: Automated TLH

trades (array): Array of trade objects containing:

  • accountId (integer)
  • sellTickerId (integer)
  • sellTickerName (string)
  • percent (decimal)
  • shares (decimal)
  • buyTickerId (integer)
  • buyTickerName (string)
  • isEquivalentTicker (boolean)
  • totalGLAmount (decimal)
  • portfolioId (integer)
  • washSaleGroup (string)
  • isTargeted (integer): 0 or 1
  • equivalentSecurityId (integer)

filter (object): Same format as opportunity identification

taxHarvestingFilterBatchId (string): Batch ID from previous step

3. Trade Generation

POST /v1/tradetool/taxLossHarvesting/action/generateTrade

Creates validated trades

Required Parameters

All parameters from validation endpoint, plus:

instanceNotes (string): Optional description

tradeToolSelection (integer):

  1. Portfolio
  2. Account

tradeInstanceType (integer): Use 8 for TLH

tradeInstanceSubType (integer):

  • 1: Automated TLH
  • 7: Manual Gains
  • 8: Manual Losses

Best Practices

  1. Always validate trades before generating them
  2. Include descriptive notes for audit purposes
  3. Check individual trade status in responses
  4. Handle errors appropriately:
    • 401: Check authentication
    • 400: Verify request parameters
    • 500: Retry with exponential backoff

Example Payloads

1A. Manual TLH

Request:

{
  "isFullPositionHarvest": false,
  "ignoreDoNotTLH": false,
  "isGainHarvesting": true,
  "amount": null,
  "term": 2,
  "securityTypeIds": [5],
  "securityIds": [292],
  "minimumGainLoss": null,
  "minimumGainLossAmount": null,
  "minimumGainLossPercent": null,
  "type": "portfolio",
  "ids": [38598]
}

Response:

{
 "batchId": "q2EOT0S-7odlHuW6Ak6Db",
 "trades": [
   {
     "GLPercent": 100,
     "LTGLAmount": 0,
     "STGLAmount": 19925.02442,
     "accountId": 64272,
     "accountName": "Rogers 08, Steve",
     "accountNumber": "SR08",
     "accountType": "TAXABLE",
     "accountValue": 385434.125782,
     "cashValue": 4.59,
     "costPrice": 86.87,
     "currentPercent": 5.76,
     "currentShares": 229.366,
     "currentValue$": 22218.68442,
     "custodian": "Schwab",
     "custodianId": 4,
     "securityId": 292,
     "securityName": "Walt Disney Co",
     "symbol": "DIS",
     "tlhSecurityId": 14906,
     "tlhSecurityName": "FS Bancorp Inc",
     "tlhSecuritySymbol": "FSBW",
     "tlhSellingPercent": 100,
     "totalGLAmount": 19925.02442,
     "tradeAmount": 22218.68
   }
 ]
}

1B. Automated TLH

Request:

{
 "amount": null,
 "batchId": "rm8hAuojO6w3GBmDle87N",
 "bucketArray": [
   [38598]
 ],
 "ids": [38598],
 "ignoreDoNotTLH": null,
 "isFullPositionHarvest": null,
 "isGainHarvesting": null,
 "minimumGainLoss": null,
 "minimumGainLossAmount": null,
 "minimumGainLossPercent": null,
 "securityIds": [880],
 "securityTypeIds": [5],
 "term": 1,
 "type": "portfolio"
}

Response:

{
 "trades": [
   {
     "accountId": 64282,
     "accountName": "Rebalance 11,",
     "accountNumber": "REBAL11",
     "symbol": "VTV",
     "securityName": "Vanguard Value ETF",
     "sellPrice": 164.93,
     "costPrice": -35.07,
     "totalGLAmount": -2875.74,
     "tlhSellingPercent": 100,
     "portfolioId": 38598,
     "shares": 82,
     "tradeAmount": 13524.26,
     "STGLAmount": 0,
     "LTGLAmount": -2875.74,
     "GLPercent": -17.535,
     "accountType": "TAXABLE",
     "custodian": "Schwab"
   }
 ],
 "haveDoNotTLHSecurity": false,
 "batchId": "rm8hAuojO6w3GBmDle87N"
}

2. Trade Validation

Request:

{
 "allowWashSaleOnSell": false,
 "isTaxHarvesting": true,
 "trades": [{
   "accountId": 64272,
   "sellTickerId": 292,
   "sellTickerName": "DIS",
   "percent": 100,
   "shares": 229.366,
   "buyTickerId": 14906,
   "buyTickerName": "FS Bancorp Inc",
   "isEquivalentTicker": false,
   "totalGLAmount": 19925.02442,
   "portfolioId": 38598,
   "washSaleGroup": "27531",
   "isTargeted": 1,
   "equivalentSecurityId": null
 }],
 "filter": {
   "isFullPositionHarvest": false,
   "ignoreDoNotTLH": false,
   "isGainHarvesting": true,
   "amount": null,
   "term": 2,
   "securityTypeIds": [5],
   "securityIds": [292],
   "type": "portfolio",
   "ids": [38598],
   "isViewOnly": false
 },
 "taxHarvestingFilterBatchId": "q2EOT0S-7odlHuW6Ak6Db",
 "overridePreference": {
   "enableVSP": null
 }
}

Response:

{
 "cashValuePostTrade": {
   "64272": 90060.28
 },
 "trades": [
   {
     "accountId": 64272,
     "buyTickerId": 14906,
     "buyTickerName": "FSBW",
     "buyTradeAmount": 22218.67,
     "buyTradeShares": 633.191,
     "cashValuePostTrade": 90060.273774,
     "errorMessage": "",
     "isValidTrade": 1,
     "percent": 100,
     "reason": "Successfully validated Trades",
     "sellTickerId": 292,
     "sellTickerName": "DIS",
     "sellTradeAmount": 22218.68,
     "sellTradeShares": 229.366,
     "status": "success",
     "warningMessage": "Sell Trade (DIS): Portfolio max gain and carry forward loss amount of 200 has been reached."
   }
 ]
}

3. Trade Generation

Request:

{
 "allowWashSaleOnSell": false,
 "isTaxHarvesting": true,
 "trades": [
   {
     "accountId": 64272,
     "sellTickerId": 292,
     "sellTickerName": "DIS",
     "percent": 100,
     "shares": 229.366,
     "buyTickerId": 14906,
     "buyTickerName": "FS Bancorp Inc",
     "isEquivalentTicker": false,
     "totalGLAmount": 19925.02442,
     "portfolioId": 38598,
     "washSaleGroup": "27531",
     "isTargeted": 1,
     "equivalentSecurityId": null
   }
 ],
 "filter": {
   "isFullPositionHarvest": false,
   "ignoreDoNotTLH": false,
   "isGainHarvesting": true,
   "amount": null,
   "term": 2,
   "securityTypeIds": [5],
   "securityIds": [292],
   "type": "portfolio",
   "ids": [38598],
   "isViewOnly": false
 },
 "instanceNotes": "TLH Instance",
 "taxHarvestingFilterBatchId": "Sst1x8ujrbmGHIIcLPBYo",
 "overridePreference": {
   "enableVSP": null
 },
 "tradeToolSelection": 1,
 "tradeInstanceType": 8,
 "tradeInstanceSubType": 7
}

Response:

{
 "trades": [
   {
     "sellTickerId": 292,
     "sellTickerName": "DIS",
     "buyTickerId": 14906,
     "percent": 100,
     "accountId": 64272,
     "portfolioId": 38598,
     "status": "success",
     "reason": "Successfully Swap Trades.",
     "isValidTrade": 1,
     "errorMessage": ""
   }
 ],
 "instanceId": 1893,
 "cashValuePostTrade": {
   "64272": 90060.28
 }
}

Rate Limits

  • Maximum 500 portfolios/accounts per batch
  • Maximum 2 buckets per automated TLH request