Portfolio API

19 endpoints in this group.

GET /api/portfolio/archive-stats

Archive Stats

Row counts and date range for the snapshot and intraday tables.

Parameters

NameInTypeRequiredDescription
tenant_idquery—optional
account_idquery—optional
account_idsquery—optional

Responses

CodeDescription
200Successful Response
422Validation Error

POST /api/portfolio/backfill/csv

Backfill From Csv

Deprecated stub — use /api/portfolio/backfill/upload instead.

Parameters

NameInTypeRequiredDescription
tenant_idquery—optional

Responses

CodeDescription
200Successful Response
422Validation Error

POST /api/portfolio/backfill/save

Backfill Save

Request body: application/json

Responses

CodeDescription
201Successful Response
422Validation Error

POST /api/portfolio/backfill/upload

Backfill Upload

Upload a CSV of historical position snapshots and upsert into daily_position_snapshots.

Expected CSV columns (flexible header matching): symbol, date, quantity, avg_cost, market_price, market_value Optional: unrealized_pnl, day_change_pct, source

Parameters

NameInTypeRequiredDescription
yearqueryintegeroptional
source_typequerystringoptional
tenant_idquery—optional

Request body: multipart/form-data

Responses

CodeDescription
201Successful Response
422Validation Error

DELETE /api/portfolio/data

Delete Year Data

Soft-delete a year's realized_trades + daily_position_snapshots.

Both tables get deleted_at = now() on still-active rows. No row is physically removed (see migration 0115_soft_delete_realized_dps).

Parameters

NameInTypeRequiredDescription
tenant_idquery—optional
yearqueryintegerrequired

Responses

CodeDescription
200Successful Response
422Validation Error

POST /api/portfolio/eod

Run Eod Ingestion

Trigger end-of-day broker position fetch + snapshot save.

A successful response can represent either saved positions or a verified empty broker account. When the broker confirms that an account has no positions, the response includes reconciled_empty_accounts and reconciled_empty_positions; this is a successful reconciliation that retires stale snapshot rows, not a broker failure. If one or more connected brokers cannot be read or reconciled while the rest of the run continues, the response includes degraded: true, reason: "portfolio_broker_partial", and skipped_brokers with the affected broker names. These fields may accompany the normal snapshot result or the no-positions message.

Storage semantics: successful empty reconciliation persists zero-quantity snapshot markers for the verified account and snapshot date. These markers establish the latest-date boundary; they are not open holdings. Consumers must select the latest boundary including markers, then exclude zero quantities from returned holdings or holding-derived subscription interest. Excluding markers before selecting the latest date can resurrect an older holding. Explicit watchlist interest remains valid even when a holding is closed.

Parameters

NameInTypeRequiredDescription
tenant_idquery—optional

Responses

CodeDescription
200Successful Response
422Validation Error

GET /api/portfolio/eod/run-log

Get Eod Run Log

Return recent EOD snapshot runs grouped by snapshot_date.

Parameters

NameInTypeRequiredDescription
tenant_idquery—optional
account_idquery—optional
account_idsquery—optional
limitqueryintegeroptional

Responses

CodeDescription
200Successful Response
422Validation Error

POST /api/portfolio/eod/trigger

Run Eod Ingestion

Trigger end-of-day broker position fetch + snapshot save.

This alias has the same response contract as POST /api/portfolio/eod. A verified empty account can return reconciled_empty_accounts and reconciled_empty_positions. A partial broker failure returns degraded: true, reason: "portfolio_broker_partial", and skipped_brokers while preserving successful work from other brokers.

Parameters

NameInTypeRequiredDescription
tenant_idquery—optional

Responses

CodeDescription
200Successful Response
422Validation Error

GET /api/portfolio/fees-summary

Fees Summary

Parameters

NameInTypeRequiredDescription
daysqueryintegeroptional
account_hashquery—optional

Responses

CodeDescription
200Successful Response
422Validation Error

GET /api/portfolio/pnl

Get Pnl Series

Daily P&L time series — P0-8: limit capped at 100.

G10 (mobile#150): supports keyset cursor pagination for infinite-scroll.

The series is grouped by snapshot_date and ordered ASC (chronological). Pass cursor from the previous page's next_cursor to fetch the next chunk; the server emits next_cursor only when a full page was returned (len(rows) == limit). Cursor is opaque — a base64url JSON blob whose payload is {"d": "YYYY-MM-DD"} (date-only, since rows are grouped by date and have no row-id).

The page dependency provides limit (1..100) + a generic cursor query param for shape-parity with the blotter endpoint; we decode it locally rather than via decode_cursor because pnl keyset is one-column not two.

Parameters

NameInTypeRequiredDescription
tenant_idquery—optional
account_idquery—optional
account_idsquery—optional
start_datequery—optional
end_datequery—optional
limitqueryintegeroptionalPage size (max 100). Legacy — prefer ?page_size=.
page_sizequery—optionalCanonical page size. Must be one of 25, 50, 100. When supplied, overrides ?limit=.
cursorquery—optionalOpaque pagination cursor from a previous page's next_cursor.

Responses

CodeDescription
200Successful Response
422Validation Error

GET /api/portfolio/positions/{symbol}

Get Position By Symbol

Latest daily snapshot row for one symbol.

Returns 404 when the tenant has no snapshot for that symbol so the mobile client can render an empty-state without parsing. Returns 200 with the same shape as /snapshots (singular snapshot key + snapshot_date echo) so PositionDetailScreen reuses the existing dashboard summary-row parser.

Parameters

NameInTypeRequiredDescription
symbolpathstringrequired
tenant_idquery—optional
account_idquery—optional
account_idsquery—optional

Responses

CodeDescription
200Successful Response
422Validation Error

GET /api/portfolio/realized-from-orders

Get Realized From Orders

Return FIFO realized lots derived from filled local orders.

Parameters

NameInTypeRequiredDescription
tenant_idquery—optional
account_idquery—optional
account_idsquery—optional
yearquery—optional
symbolquery—optional

Responses

CodeDescription
200Successful Response
422Validation Error

GET /api/portfolio/realized-kpis

Get Realized Kpis

Day/MTD/YTD realized gain/loss for the live Portfolio KPI strip.

Parameters

NameInTypeRequiredDescription
tenant_idquery—optional
account_idquery—optional
account_idsquery—optional
sourcequerystringoptionalbroker

Responses

CodeDescription
200Successful Response
422Validation Error

GET /api/portfolio/realized-trades

Get Realized Trades

Return realized trades from uploads or connected-broker lot projections.

Parameters

NameInTypeRequiredDescription
tenant_idquery—optional
account_idquery—optional
account_idsquery—optional
yearquery—optional
symbolquery—optional
sourcequery—optional
limitquery—optional
pagequeryintegeroptional1-based page index.
page_sizequeryintegeroptionalPage size — must be one of 25, 50, 100.

Responses

CodeDescription
200Successful Response
422Validation Error

POST /api/portfolio/realized-trades/sync

Sync Realized Trades

Repair Schwab realized-trade rows from already-stored broker data.

Parameters

NameInTypeRequiredDescription
account_idquery—optional
account_idsquery—optional
force_rebuild_closed_lotsquerybooleanoptional

Responses

CodeDescription
200Successful Response
422Validation Error

GET /api/portfolio/snapshots

Get Snapshots

Parameters

NameInTypeRequiredDescription
tenant_idquery—optional
account_idquery—optional
account_idsquery—optional
account_numberquery—optional
start_datequery—optional
end_datequery—optional
as_of_datequery—optional
latest_onlyquerybooleanoptional
refresh_livequerybooleanoptionalWhen true with latest_only, foreground-refresh connected broker positions before reading.
limitqueryintegeroptional

Responses

CodeDescription
200Successful Response
422Validation Error

POST /api/portfolio/snapshots

Save Snapshots

Request body: application/json

Responses

CodeDescription
201Successful Response
422Validation Error

POST /api/portfolio/snapshots/archive

Archive Snapshots

  1. Move daily_position_snapshots older than keep_snapshot_years to archive table.
  2. Rollup of old 5-min intraday bars into the shared market_data_daily is skipped (plan slice 1m, ).
  3. Intraday bars beyond the retention window are retained, not purged.

Parameters

NameInTypeRequiredDescription
tenant_idquery—optional
keep_snapshot_yearsqueryintegeroptional
keep_intraday_yearsqueryintegeroptional

Responses

CodeDescription
200Successful Response
422Validation Error

DELETE /api/portfolio/trades

Delete Trades

Soft-delete realized_trades for the given tenant + year.

Sets deleted_at = now() on already-active rows (idempotent — rows that are already soft-deleted are skipped). Returns the number of rows that transitioned to soft-deleted in this call.

Parameters

NameInTypeRequiredDescription
tenant_idquery—optional
yearqueryintegerrequired

Responses

CodeDescription
200Successful Response
422Validation Error

Occasional BlitzPulse product updates and offers, sent only if you tick the box and confirm your address.

How we handle your email

Footer Disclaimer BlitzPulse is educational decision support only. Not investment, tax, or legal advice. Not a broker. Trade at your own risk.