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.
| Param | Type | Description |
|---|---|---|
from | date | YYYY-MM-DD or ISO 8601, in your user timezone. |
to | date | Same. |
account / account_id | int or string | Either alias works. account_id (int) or account name (case-insensitive). |
symbol | string | Exact match, case-insensitive (e.g. NQ, MNQ, EURUSD). |
direction | enum | long or short. |
strategy / strategy_id | string or int | Either alias works. Pass an integer to filter by id; a string to match strategy name (case-insensitive). |
setup | string | Setup name, case-insensitive. |
grade / trade_quality_grade | enum | Either alias works. A+, A, B, C, F. |
tag | string | Tag name (any tag on the trade matches). |
outcome | enum | win, loss, breakeven (uses your account’s BE offsets). |
pnl_min / net_pnl_gte | decimal | Either alias works. Minimum net P&L. |
pnl_max / net_pnl_lte | decimal | Either alias works. Maximum net P&L. |
is_copy | bool | true to only return copy-trades, false to exclude them. |
include_archived | bool | Default false; archived trades are hidden. |
limit | int | 1-200, default 50. |
cursor | string | Opaque 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.
| Field | Type | Required | Notes |
|---|---|---|---|
account_id | int | yes | Must be an account you own and not archived. |
trade_date | ISO 8601 datetime | yes | Rejected if > tomorrow or < year 2000. |
symbol | string (1–50) | yes | Stored uppercased. |
direction | enum | yes | long or short. |
net_pnl | decimal | yes | ±999,999,999,999,999.99. The server does not compute this from gross/fees. |
gross_pnl | decimal | no | Defaults to net_pnl + fees. |
fees | decimal | no | Defaults to 0. |
quantity | decimal ≥ 0 | no | Up 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). |
multiplier | decimal | no | Defaults to 1.0. Up to ±99,999,999.99. |
asset_type | string | no | Defaults to futures. Must be a known type (futures, forex, stocks, options, crypto). |
asset_config | object | no | Asset-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_price | decimal | no | Stop-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_points | decimal | no | ±999,999,999,999,999.99. |
rr_expected / rr_realized | decimal | no | ±99,999,999.99. |
holding_time | int (seconds) | no | 0 to 2,147,483,647. |
market_condition | enum | no | trending, ranging, choppy, breakout. |
trading_session | enum | no | asian, london, newyork, overlap. |
trade_quality_grade | enum | no | A+, 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. |
volume | enum | no | very_low, low, below_average, average, above_average, high, very_high. |
emotional_state | string | no | Must 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. |
bias | enum | no | Bullish, Bearish, Neutral (case-sensitive). Matches the journal UI dropdown. |
confidence_level | int 1–10 | no | Database constraint enforces the range. |
exit_type | enum | no | Take 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_quality | string ≤ 100 | no | Free-form. |
news_events | string ≤ 65535 | no | Free-form text. |
strategy_id | int | no | Must be a strategy you own. If account-scoped, must match this account; if group-scoped, this account must be a group member. |
setup_id | int | no | Must belong to strategy_id. |
thought_process / mistakes_made / learning_notes / general_notes | string ≤ 65535 | no | Plain 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.
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 unlessincluding_copies=truewas 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 field | Type | Required | Notes |
|---|---|---|---|
file | file | yes | JPEG, PNG, or PDF. Max 10 MB. Both extension and MIME type are checked. |
label | string ≤ 100 | no | Caption for the screenshot. |
sort_order | int | no | Defaults 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:
| Field | Type | Required | Notes |
|---|---|---|---|
entries | array | at least one of entries/exits | Entry fills. Each item: {price, quantity, execution_time?, sort_order?}. |
exits | array | at least one of entries/exits | Exit fills. Same shape as entries. |
price | decimal > 0 | yes | Bounded 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). |
quantity | decimal > 0 | yes | Same bound as price. |
execution_time | ISO 8601 datetime | no | UTC. Same format as trade_date. null or omitted is allowed. |
sort_order | int | no | Defaults 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:
priceandquantityare stricter than the website’s internal save: zero, negative, or sub-precision values return400rather 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_timeuses the same ISO 8601 envelope as POST/tradestrade_date: rejected if more than 24h in the future or before2000-01-01.sort_orderis accepted as-is from the caller (no normalization). Duplicate or sparse values tie-break byexecution_idASC 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:
| Status | Code | Meaning |
|---|---|---|
| 200 | (no error code) | Replace succeeded. Body returns the new execution list. |
| 400 | validation_error | Bad body shape, missing required fields, or invalid value (price/qty ≤ 0, bad datetime, etc.). |
| 404 | not_found | No trade with that trade_number belonging to you, OR the trade is archived (ret_archived = 1). |
| 409 | idempotency_conflict | Same Idempotency-Key reused with a different body. |