API
View as Markdown

Trades

Read trades with filters and pagination, fetch full detail, attach screenshots / tags / confirmations, and create / update / archive trades through the write endpoints below.

GET /trades

Scope: read:trades

List your trades, newest first, with cursor pagination. Default 50 per page, max 200.

ParamTypeDescription
fromdateYYYY-MM-DD or ISO 8601, in your user timezone.
todateSame.
account / account_idint or stringEither alias works. account_id (int) or account name (case-insensitive).
symbolstringExact match, case-insensitive (e.g. NQ, MNQ, EURUSD).
directionenumlong or short.
strategy / strategy_idstring or intEither alias works. Pass an integer to filter by id; a string to match strategy name (case-insensitive).
setupstringSetup name, case-insensitive.
grade / trade_quality_gradeenumEither alias works. A+, A, B, C, F.
tagstringTag name (any tag on the trade matches).
outcomeenumwin, loss, breakeven (uses your account’s BE offsets).
pnl_min / net_pnl_gtedecimalEither alias works. Minimum net P&L.
pnl_max / net_pnl_ltedecimalEither alias works. Maximum net P&L.
is_copybooltrue to only return copy-trades, false to exclude them.
include_archivedboolDefault false; archived trades are hidden.
limitint1-200, default 50.
cursorstringOpaque token from a previous response’s meta.next_cursor.

Each trade in the response includes trade_number, trade_date, symbol, direction, net_pnl, account, grade, strategy, setup, top_tag, is_copy.

GET /trades/{trade_number}

Scope: read:trades (also read:screenshots to include the screenshots array)

Full trade detail. The response includes:

  • Identity: trade_number, trade_date, symbol, asset_type, direction, quantity, multiplier
  • Risk markers: sl_price, tp_price, rr_expected, rr_realized
  • P&L: gross_pnl, fees, net_pnl, total_points
  • Context: holding_time, market_condition, trading_session, volume, news_events
  • Subjective: grade, emotional_state, bias, confidence_level, setup_quality, exit_type
  • Notes: thought_process, mistakes_made, learning_notes, general_notes
  • Relations: account (id+name+currency), strategy (name), setup (name)
  • Copy info: is_copy, is_copy_source, source_trade_number
  • Nested arrays: executions[], tags[], confirmations[], screenshots[], protocol_responses[]
  • Audit: created_at, updated_at

Copy-trade aware: shared data (confirmations, screenshots, protocol responses) is resolved through the source trade automatically. The trade_number is the per-user counter shown in the journal UI — not an internal ID.

protocol_responses[] (requires read:protocols scope on the key — omitted otherwise) returns at most one entry per protocol type (pre_trade, in_trade, post_trade). Each row carries response_id, protocol_id, protocol_type, protocol_name, status, completion_pct, completed_at, and a source field indicating "trade" (saved directly on this trade) or "source" (inherited from the copy’s source trade). For per-item answers, fetch the response details via GET /protocols/{protocol_id}/responses?trade_number={n}.

GET /trades/{trade_number}/screenshots

Scope: read:screenshots

List screenshot metadata for a trade. Each entry has a download_url for the binary endpoint.

GET /trades/{trade_number}/screenshots/{screenshot_id}

Scope: read:screenshots

Stream the screenshot file. Returns binary bytes with the appropriate Content-Type (image/png, image/jpeg, application/pdf). Cached for 5 minutes (screenshots are immutable once uploaded).


POST /trades

Scope: write:trades

Create a single trade row. Returns the full trade detail. Out of scope at create time: tags, confirmations, screenshots, executions, copy-trade duplication. Use the dedicated endpoints below (PUT /trades/{n}/tags, PUT /trades/{n}/confirmations, POST /trades/{n}/screenshots, PUT /trades/{n}/executions) to attach related data after the create.

FieldTypeRequiredNotes
account_idintyesMust be an account you own and not archived.
trade_dateISO 8601 datetimeyesRejected if > tomorrow or < year 2000.
symbolstring (1–50)yesStored uppercased.
directionenumyeslong or short.
net_pnldecimalyes±999,999,999,999,999.99. The server does not compute this from gross/fees.
gross_pnldecimalnoDefaults to net_pnl + fees.
feesdecimalnoDefaults to 0.
quantitydecimal ≥ 0noUp to 9,999,999,999.99 (DECIMAL(18,8); upper bound capped one digit below the theoretical column max because PHP doubles can’t represent the full 8-decimal mantissa exactly).
multiplierdecimalnoDefaults to 1.0. Up to ±99,999,999.99.
asset_typestringnoDefaults to futures. Must be a known type (futures, forex, stocks, options, crypto).
asset_configobjectnoAsset-type-specific config stored as JSON. Forex: { "lot_type": "standard"|"mini"|"micro", "pip_value": number }. Options: { "option_type": "call"|"put", "strike_price": number, "expiration": "YYYY-MM-DD" }. Ignored for futures/stocks/crypto. Per-field validation runs against the same schema the website uses; bad fields return 400 validation_error with error.details.asset_config_errors.
sl_price / tp_pricedecimalnoStop-loss and take-profit prices. Must be strictly > 0 (zero / negative rejected with 400; pass null to clear). DECIMAL(18,8) on the column — the API caps at 9,999,999,999.99 on the high end and 0.00000001 on the low end (anything below the floor would silently round to 0 in the DB).
total_pointsdecimalno±999,999,999,999,999.99.
rr_expected / rr_realizeddecimalno±99,999,999.99.
holding_timeint (seconds)no0 to 2,147,483,647.
market_conditionenumnotrending, ranging, choppy, breakout.
trading_sessionenumnoasian, london, newyork, overlap.
trade_quality_gradeenumnoA+, A, B, C, F. Note: the response serializer renames this field to grade on the trade object — you POST trade_quality_grade and read it back as trade.grade.
volumeenumnovery_low, low, below_average, average, above_average, high, very_high.
emotional_statestringnoMust match an entry in your /emotions catalog (the same list the journal UI dropdown reads). Use GET /api/v1/emotions for the valid values; add custom emotions via the website’s manage-emotions modal.
biasenumnoBullish, Bearish, Neutral (case-sensitive). Matches the journal UI dropdown.
confidence_levelint 1–10noDatabase constraint enforces the range.
exit_typeenumnoTake Profit, Stop Loss, Trailing Stop, Partial / Scale Out, Breakeven, Time-Based, Manual (case-sensitive). Matches the journal UI dropdown so the value renders back in the edit form.
setup_qualitystring ≤ 100noFree-form.
news_eventsstring ≤ 65535noFree-form text.
strategy_idintnoMust be a strategy you own. If account-scoped, must match this account; if group-scoped, this account must be a group member.
setup_idintnoMust belong to strategy_id.
thought_process / mistakes_made / learning_notes / general_notesstring ≤ 65535noPlain text. The journal UI applies its own HTML sanitization for rich-text input; the API stores whatever you send verbatim.
curl -X POST https://app.tradavity.com/api/v1/trades \
  -H "Authorization: Bearer tvty_YOUR_KEY_HERE" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "account_id": 192,
    "trade_date": "2026-05-10T14:32:00Z",
    "symbol": "MNQ",
    "direction": "long",
    "quantity": 1,
    "multiplier": 2,
    "net_pnl": 50.0,
    "fees": 1.24,
    "trade_quality_grade": "A",
    "general_notes": "Clean breakout, executed plan."
  }'

Returns: 201 Created with { data: { trade: {...} } }. Use data.trade.trade_number in subsequent calls (chain tags / confirmations / screenshots).

Plan limits: POST /trades respects the same daily/monthly trade caps the website enforces. If your plan’s trade cap is exhausted, the API returns 429 limit_reached with error.details.usage showing day/month counters. Pro plans return 429 only on hard caps (Free plans hit a soft daily/monthly cap).

PATCH /trades/{trade_number}

Scope: write:trades

Update qualitative fields on an existing trade. Send only the fields you want to change — everything else is left alone.

Editable fields: notes (4 separate fields: thought_process, mistakes_made, learning_notes, general_notes), trade_quality_grade, emotional_state, bias, confidence_level, market_condition, trading_session, exit_type, setup_quality, news_events, volume, strategy_id, setup_id, sl_price, tp_price.

SL / TP: sl_price and tp_price are per-trade (not propagated to copies, mirroring the website edit flow). Send a strictly-positive number to set, null to clear. Same DECIMAL(18,8) bounds as POST (0.00000001 floor, 9,999,999,999.99 ceiling). Zero, negative, or sub-precision values return 400.

Copy-trade propagation

If the trade is part of a copy-trade decision, the fields strategy_id, setup_id, and trade_quality_grade are propagated to the source trade and every copy. Notes, emotional state, bias, and confidence stay per-trade. The response includes data.propagated: true/false.

Account-scoped strategies are validated per copy: a strategy bound to account_id=72 applied to a copy on account_id=73 will null out strategy_id and setup_id on that copy, not error out.

Editing the executions, screenshots, tags, or confirmations of a trade is done via dedicated endpoints (below). Core fields like trade_date, symbol, direction, and PnL cannot be edited through PATCH; create a new trade and archive the old one if the original was wrong.

curl -X PATCH https://app.tradavity.com/api/v1/trades/581 \
  -H "Authorization: Bearer tvty_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "trade_quality_grade": "A+",
    "emotional_state": "calm",
    "general_notes": "Trade went exactly to plan."
  }'

DELETE /trades/{trade_number}

Scope: write:trades

Soft-delete (archive) a trade. The row stays in the database with ret_archived = 1. Recovery is via the data-retention UI within 30 days; after that it is hard-purged by cron.

Optional query param including_copies=true cascades the archive to every copy of the same decision, but only when the target is the copy source. If you pass including_copies=true on a copy that is not a source, you get 400 validation_error.

The 204 response includes two informational headers:

  • X-Trades-Archived: how many trades the call archived (1 unless cascading).
  • X-Copies-Archived: how many copies were cascaded (0 unless including_copies=true was set on a source).

Re-deleting an already-archived trade returns 404 not_found with the message “Trade is already archived.”

curl -X DELETE "https://app.tradavity.com/api/v1/trades/581?including_copies=true" \
  -H "Authorization: Bearer tvty_YOUR_KEY_HERE"

POST /trades/{trade_number}/screenshots

Scope: write:screenshots

Multipart upload. Attaches a screenshot to a trade. Copy trades store screenshots on the source — uploading to a copy actually writes the file under the source’s account directory and is visible from every copy via GET.

Form fieldTypeRequiredNotes
filefileyesJPEG, PNG, or PDF. Max 10 MB. Both extension and MIME type are checked.
labelstring ≤ 100noCaption for the screenshot.
sort_orderintnoDefaults to one past the highest existing sort_order on the trade.

Storage is gated two ways: the account-wide quota (your plan’s total file budget) and a per-trade screenshot count cap. Free plans: 1 screenshot per trade. Pro plans: 3 screenshots per trade. Either ceiling returns 413 limit_reached with error.details showing which cap fired.

The Idempotency-Key for screenshot uploads hashes the file’s SHA-256 plus the label and sort_order — a true retry of the same file with the same key returns the cached response. A different file under the same key returns 409 idempotency_conflict.

JPEG and PNG uploads are run through an image optimizer; the response includes optimized: true and savings_bytes when compression succeeded. PDFs are stored as-is.

curl -X POST "https://app.tradavity.com/api/v1/trades/581/screenshots" \
  -H "Authorization: Bearer tvty_YOUR_KEY_HERE" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F "file=@chart.png" \
  -F "label=Entry breakout"

Returns: 201 Created. Body data contains screenshot_id, trade_number, file_size, original_size, file_type, width, height, label, sort_order, uploaded_at, download_url, plus optimized and savings_bytes when compression ran.

DELETE /trades/{trade_number}/screenshots/{screenshot_id}

Scope: write:screenshots

Soft-delete a screenshot. The on-disk file stays; storage quota is freed immediately. Re-deleting an archived screenshot returns 404.

curl -X DELETE "https://app.tradavity.com/api/v1/trades/581/screenshots/3300" \
  -H "Authorization: Bearer tvty_YOUR_KEY_HERE"

PUT /trades/{trade_number}/tags

Scope: write:tags

Replace the entire tag set on a trade. Pass an empty array to clear all tags.

Tags are not propagated to copies — each copy keeps its own tag set. If you want the same tags on every copy of a decision, call this endpoint on each copy.

curl -X PUT "https://app.tradavity.com/api/v1/trades/581/tags" \
  -H "Authorization: Bearer tvty_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"tag_ids": [19, 109, 804]}'

Cap of 100 tags per trade. Cross-user tag_ids return 400 validation_error with error.details.unknown_tag_ids listing the offenders.

PUT /trades/{trade_number}/confirmations

Scope: write:trades

Replace the confirmation response set. Confirmations are stored on the source trade — setting them on a copy mutates the source’s rows, and a GET on any copy reads from the source.

curl -X PUT "https://app.tradavity.com/api/v1/trades/581/confirmations" \
  -H "Authorization: Bearer tvty_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "confirmations": [
      {"confirmation_id": 215, "was_present": true,  "notes": "clean entry"},
      {"confirmation_id": 216, "was_present": false}
    ]
  }'

Each item must include confirmation_id (int). was_present (bool) is optional and defaults to true when omitted. Optional notes is a string up to 65,535 bytes. Cap of 200 rows per trade. Cross-user confirmation_id values are rejected with 400 validation_error and error.details.unknown_confirmation_ids.

GET /trades/{trade_number}/executions

Scope: read:trades

Returns every entry + exit fill on the trade. Each row carries execution_id, type (entry or exit), price, quantity, execution_time (ISO 8601 UTC or null), and sort_order. Auto-synced trades will already have populated executions from the broker; manually-created trades have an empty list until you PUT some.

PUT /trades/{trade_number}/executions

Scope: write:trades

Replace the full execution list on a trade in one call. Mirrors the website’s edit-trade flow (features/journal/jrnl_update_trade.php via jrnl_updateExecutions): existing rows are deleted and the new set is inserted inside one transaction.

Executions are per-trade, not shared via source_trade_id — each copy in a copy-trade decision carries its own fills (matches the website edit form).

curl -X PUT "https://app.tradavity.com/api/v1/trades/586/executions" \
  -H "Authorization: Bearer tvty_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "entries": [
      {"price": 18000.25, "quantity": 1, "execution_time": "2026-05-11T14:30:00Z"},
      {"price": 18002.50, "quantity": 1, "execution_time": "2026-05-11T14:31:15Z"}
    ],
    "exits": [
      {"price": 18010.00, "quantity": 2, "execution_time": "2026-05-11T14:45:00Z"}
    ]
  }'

Body fields:

FieldTypeRequiredNotes
entriesarrayat least one of entries/exitsEntry fills. Each item: {price, quantity, execution_time?, sort_order?}.
exitsarrayat least one of entries/exitsExit fills. Same shape as entries.
pricedecimal > 0yesBounded by DECIMAL(18,8). API caps at 9,999,999,999.99 on the high end (one digit below the theoretical column max, kept in float-safe range) and 0.00000001 on the low end (anything smaller would silently round to 0 in the DB).
quantitydecimal > 0yesSame bound as price.
execution_timeISO 8601 datetimenoUTC. Same format as trade_date. null or omitted is allowed.
sort_orderintnoDefaults to the array index inside its group.

Cap of 200 total executions per trade (entries + exits combined). Sending a body with neither entries nor exits returns 400 validation_error — pass {"entries": [], "exits": []} explicitly to clear everything. The 200 OK response returns the freshly-read list with the new execution_ids assigned by the database.

Concurrent-write semantics. Each PUT replaces the full execution set inside one transaction (DELETE + INSERT). N concurrent PUTs against the same trade serialize at the DB and resolve last-write-wins; every call receives 200 OK with the body it tried to write echoed back. Subsequent reads will only see the last commit. If you need read-after-write guarantees, send sequentially or use the same Idempotency-Key so the second call replays the first’s cached response instead of overwriting.

Field constraints:

  • price and quantity are stricter than the website’s internal save: zero, negative, or sub-precision values return 400 rather than being silently dropped. Both bounded by DECIMAL(18,8) — API caps at 9,999,999,999.99 (float-safe ceiling) and 0.00000001 (DB precision floor).
  • execution_time uses the same ISO 8601 envelope as POST /trades trade_date: rejected if more than 24h in the future or before 2000-01-01.
  • sort_order is accepted as-is from the caller (no normalization). Duplicate or sparse values tie-break by execution_id ASC on read — pass distinct integers if order matters.

Out of scope: this endpoint does not recompute the trade’s net_pnl, gross_pnl, or fees from the fills you write — those columns remain caller-authoritative. If editing executions implies a PnL change, PATCH the trade fields in a separate call.

Response codes:

StatusCodeMeaning
200(no error code)Replace succeeded. Body returns the new execution list.
400validation_errorBad body shape, missing required fields, or invalid value (price/qty ≤ 0, bad datetime, etc.).
404not_foundNo trade with that trade_number belonging to you, OR the trade is archived (ret_archived = 1).
409idempotency_conflictSame Idempotency-Key reused with a different body.