---
title: "Api Portfolio Import Csv Preview"
method: POST
path: "/api/portfolio/import-csv"
tags: ["Portfolio"]
---

# Api Portfolio Import Csv Preview

`POST /api/portfolio/import-csv`

## Request

- Base URL: `https://www.oxfordledge.com` (the document declares no server; this is the origin it was published from)
- URL: `https://www.oxfordledge.com/api/portfolio/import-csv`
- Auth: none declared

Parse an M1 Finance CSV export and return a structured preview.

Phase 1 of the two-phase import flow (M1-1, 2026-05-27). Does NOT
mutate the user's portfolio — returns the parsed positions +
parse-errors so the M1-2 frontend can show a confirmation diff.
The user reviews and POSTs the chosen positions to
`/api/portfolio/import-csv/commit` to actually write.

Plus-tier gated (acquisition wedge for M1 power-users migrating).

## Response `200`

Successful Response

- PortfolioImportCSVPreviewResponse: `POST /api/portfolio/import-csv` — parse-only preview response. Two-phase preview-then-commit (M1-1 2026-05-27). This endpoint accepts a multipart M1 Finance CSV upload, parses it, and returns the structured positions + errors WITHOUT mutating the portfolio. The M1-2 frontend (deferred) surfaces this in a confirmation dialog; the user reviews + clicks commit, which calls `POST /api/portfolio/import-csv/commit`. Frontend reads `positions` for the diff table, `total_value` for the summary header, `parse_errors` for the warnings list, and `row_count` for the 'X of Y rows parsed' copy. All four required.
  - `positions` ParsedCSVPosition[], required: Successfully-parsed positions. May be empty if every row failed.
    - `ticker` string, required: Normalized uppercase ticker, e.g. 'AAPL' or 'BRK.A'.
    - `quantity` number, required: Share quantity (fractional OK).
    - `cost_basis` number, nullable: Per-share cost basis USD; None for M1 tracker rows.
    - `current_value` number, nullable: Position market value USD at export time; None for tracker rows.
    - `weight` number, nullable: Position weight (current_value / total_value) 0.0-1.0; None when total_value=0.
  - `total_value` number, required: Sum of current_value across parsed positions (0 if none carry current_value).
  - `parse_errors` CSVParseError[], required: Rows that were skipped during parsing. Frontend renders as warnings.
    - `row` integer, required: 1-indexed row number in the source CSV (header is row 1).
    - `reason` string, required: Human-readable reason the row was skipped.
    - `raw` string, nullable: The raw row text (truncated to 200 chars) for user diagnostics.
  - `row_count` integer, required: Total data rows seen (parsed + skipped). Drives the 'X of Y rows parsed' copy.
  - `status` string, required: 'ok' on successful parse; the FE branches on this for the confirm-dialog gate.

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/oxfordledge/apis/oxford-ledge-api.md) · [All operations](https://skmtc.dev/oxfordledge/apis/oxford-ledge-api/llms.txt) · [OpenAPI document](https://skmtc.dev/oxfordledge/apis/oxford-ledge-api/revisions/e832a1260f0b?raw)
