Skip to main content

LP Portal

The Report Studio is portfolio-scoped: one report covers one portfolio, and its numbers are fund-level. The LP Portal is the per-investor layer on top. Use it when a single fund has several limited partners and you need each LP’s shares, contributions, and NAV rather than the fund’s. It is a different service with a different base path and a different response envelope, so read this page as a separate API from the rest of this reference.

Base path and envelope

All paths below are absolute under the production host:
The gateway routes /portfolio-management/** to this service, and authentication is the same X-API-Key header (or a Keycloak bearer). A key issued with a path allowlist needs /portfolio-management on it as well as /lp-report-studio. The response envelope is the platform-wide one, not the Studio’s ok shape:
Errors use the same envelope with isError: true and the message in message. Read data for the payload.

Two audiences, two role sets

The two sets do not overlap. A manager credential cannot call /my-portal (it resolves the caller’s own investor records by email), and a limited-partner credential cannot call anything else, including the entire Report Studio API.
If you are building a dashboard your LPs log into directly, the LP endpoints are the only ones their credential can reach. If you are building a manager-side dashboard, use the manager endpoints here plus the Studio’s /computed.

Number conventions

return_pct here is a whole-number percentage, not a decimal. 12.5 means 12.5%. This is the opposite of the Studio’s /computed, where every computed percentage is a decimal (0.125). Do not share a formatter between the two APIs.
  • Monetary values (current_nav, total_contributed, total_redeemed, net_pnl) are rounded to 2 decimal places, in the fund’s base currency. There is no reporting-currency conversion on this API.
  • nav_per_share is rounded to 8 decimal places.
  • shares is unrounded.
  • net_pnl and return_pct are signed and go negative on a losing fund.
  • return_pct is 0 (not null) when an investor has contributed nothing, so a brand-new investor is indistinguishable from a flat one by that field alone. Check total_contributed first.
  • With total_shares at zero, nav_per_share falls back to the report’s initial_share_price rather than erroring.

Manager endpoints

GET /lp_portal/investors

Query: report_id (required). Returns every investor on the report, each row being the stored investor record enriched with computed metrics:
Missing report_id returns 400; an unknown one returns 404.

POST /lp_portal/investors

string
required
The report this investor belongs to.
string
required
Investor display name. Also the key used by /ownership-history.
string
required
Must be unique per report; a duplicate returns 400.
Returns 201 with the created investor.

GET, PUT, DELETE /lp_portal/investors/{investor_id}

Read, update, or remove a single investor.

GET /lp_portal/investors/{investor_id}/nav-history

Per-investor NAV time series, reconstructed by replaying that investor’s capital events against the fund’s NAV history.
timestamp is epoch milliseconds. The two marker fields are populated on dates where an event occurred, so a chart can annotate them. An empty array means the portfolio has no NAV history yet, not that the investor is absent.

GET /lp_portal/capital-events

string
required
The report to list events for.
string
Restrict to a single investor.
string
YYYY-MM-DD. Filters on effective_date.
string
YYYY-MM-DD. Filters on effective_date.
Each event carries investor_id, event_type, amount, nav_per_share_at_event, shares_transacted, effective_date, notes, and an investor_name added for convenience ("Unknown" when the investor record is gone).

POST /lp_portal/capital-events

Records a contribution or redemption and issues or cancels shares at the NAV per share on the effective date.
string
required
The investor the event applies to.
string
required
contribution, redemption, or distribution. Anything else returns 400.
number
required
The cash amount. A zero is accepted; the field only fails validation when absent.
string
default:"today"
YYYY-MM-DD. Backdating is supported and resolves to the closest NAV on or before that date.
string
Free-form note stored on the event.
Returns 201 with the created event, the updated investor, and the NAV per share used.
This endpoint is the owner of total_shares. Recording an event immediately changes the fund’s share count and therefore every investor’s nav_per_share and current_nav, and it overwrites whatever accounting.total_shares holds in the Studio config. Never write that field yourself.An investor’s first contribution also sets their high-water mark to the NAV per share at that moment.
Business-rule failures (redeeming more shares than an investor holds, for instance) return 400 with the reason in message.

DELETE /lp_portal/capital-events/{event_id}

Deletes the event and recalculates shares for the affected investor and the fund. An unknown id returns 404. Treat this as a correction tool, not a routine operation.

GET /lp_portal/ownership-history

Query: report_id (required). Ownership percentages over time, for a stacked area chart:
Allocations are keyed by investor name, so two investors with the same name collide. Percentages are whole numbers.

GET /lp_portal/dashboard

Query: report_id (required). Fund-level roll-up of the share accounting: NAV, share count, NAV per share, and investor totals. Manager view of the same numbers the LP sees for themselves.

LP user endpoints

These resolve the caller’s own records from the email on their credential. They take no investor id, and they only ever return that LP’s own data.

GET /lp_reports/reports

The LP-perspective list of reports shared with the caller. Sensitive fields are stripped (email_addresses, invite_codes, redeemed_invite_codes are removed and replaced with a shared_with count), and the report’s prose is merged in from the Studio sidecar so an LP-facing view can render it: investment_objective, market_commentary, methodology_notes, disclaimer. This is the discovery endpoint for an LP-authenticated dashboard, the counterpart to the Studio’s GET /api/funds. Also callable with the admin role.

GET /lp_portal/my-portal

Every fund the caller is an investor on, with their own metrics on each:
The fees block is present only when the report’s show_fee_breakdown visibility toggle is on. Reports where the caller is shared but has no investor record are skipped silently, so this array can be shorter than /lp_reports/reports.

GET /lp_portal/my-portal/nav-history

Query: report_id (required). The caller’s own NAV series, same shape as the manager-side investor NAV history. 404 when the caller has no investor record on that report.

GET /lp_portal/my-portal/fund-overview

Query: report_id (required). Fund-level context for the LP, gated field by field on the report’s visibility settings:
fund_nav and nav_per_share appear only when show_fund_metrics is on; portfolio_id only when show_fund_nav_chart is on. visibility_settings is always returned, so branch on it rather than on the presence of the fields. A caller with no investor record on the report gets 403.
Visibility settings are not editable through the Studio’s /config; that block was removed from it. They live on the report record and are managed by the LP Portal fund settings, so treat them as read-only from an API client’s perspective.