Strengths, Weaknesses & Gotchas
An honest assessment of what Portfolio does well, where it falls short, and the top 10 gotchas to be aware of.
Strengths: What Portfolio Does Well
Before we get to the hard truths, it's worth understanding where Portfolio genuinely excels. Key strengths worth understanding:
Weaknesses: Where Portfolio Falls Short
These are not bugs. They are architectural limitations and missing capabilities that affect what you can realistically promise to clients.
investor_asset_commitments table was dropped in September 2025 and not replaced with an equivalent. Commitment tracking now relies on subscription records, which don't fully model the commitment-call-payment chain that PE funds use.Portfolio is a portfolio reporting and investor servicing tool, not a fund accounting system. Keeping that boundary clear during client conversations helps set the right expectations.
The Top 10 Gotchas
Non-obvious behaviors and edge cases worth knowing about. Click each card to reveal the details and the fix.
Sheets are processed in the order they appear in the Excel workbook. If a sheet references data created by a later sheet, it fails. This is a common import failure.
The dependency chain: Investors → Assets → Cash Pools → Subscriptions → Capital Call Groups → Capital Calls → Valuations → Distributions → Transfers
Groups must come before Calls. Subscriptions must come before Distributions. Parent assets must come before children.
Always arrange your workbook tabs in dependency order. If in doubt, follow the chain above. The "Shared" sheet handles sorting automatically, but dedicated sheets do not.
Both "Capital Call Groups" and "Capital Repayment Groups" use the same importer class (InvestorGroupsImport) and write to the same singleton cache. When the second Groups sheet is processed, it completely overwrites the cache -- it doesn't append.
This means if Capital Repayment Groups is processed after Capital Call Groups, the capital call group mappings are gone from memory. If Capital Calls hasn't been processed yet, it will fail because its groups are no longer in the cache.
Always process in this exact order: Capital Call Groups → Capital Calls → Capital Repayment Groups → Capital Repayments. Never reorder these four sheets.
All exists validation rules include a deleted_at IS NULL condition. If an investor or asset was soft-deleted (still in the DB but marked as deleted), any import row that references it will fail with "The investor field must exist" -- even though the record does exist in the database.
This is especially confusing because you can see the record in the database but the import insists it doesn't exist.
Before importing, check for soft-deleted records that share client_reference values with your import data. Either restore them (clear deleted_at) or use different client references. The admin UI doesn't show soft-deleted records by default, so you may need to check the database directly.
Import error messages reference row numbers from the heading row, not from the top of the sheet. Most sheets use heading row 2 (row 1 is a title/description). So "Row 7" in an error message means the 7th data row after the heading -- which might be row 9 in Excel.
Some importers override this: DistributionImport and FundTransactionsImport use heading row 2, while ValuationsReplaceImport uses heading row 1. The offset differs per sheet type.
When debugging an error, always check which row the heading is on for that sheet type. Add the heading row number to the error's row number to get the Excel row. Download the official templates to see where each heading row starts.
The "Valuations" sheet is additive only. If a valuation already exists for the same asset + date + investor combination, the import fails. This catches accidental duplicate imports, but it also means you can't update a valuation that was imported with the wrong amount.
The "Valuations - Replace" sheet will create new valuations or overwrite existing ones for the same date. But it has a different heading row (row 1 instead of row 2), which trips people up when switching between sheets.
Use "Valuations" for initial imports. Use "Valuations - Replace" for corrections or ongoing updates. When switching, remember to adjust for the different heading row offset. The all - replace investor value on the Valuations sheet also works for replacing.
When you configure an asset, you choose a subscription type: drawdown, units_assigned, or fully_paid_up. This applies to all investors in that asset. You cannot have Alice on a drawdown subscription and Bob on a units-assigned subscription in the same fund.
This surprises people who expect per-investor flexibility, especially for funds that have different share classes with different subscription mechanics.
If you need different subscription types for different investors, create separate assets (e.g., "Fund I - Class A" and "Fund I - Class B") with a parent-child relationship. Each child asset can have its own subscription type.
These are two different enums that sound like they should be the same thing. A TransactionType describes what happened (SUBSCRIPTION, DISTRIBUTION, CAPITAL_CALL, etc.). A CashFlowType describes how the calc service categorizes the cash movement (DRAWDOWN, REDEMPTION, DIVIDEND, etc.).
The mapping is not 1:1. A SUBSCRIPTION transaction creates DRAWDOWN cash flows. A DISTRIBUTION transaction can create REDEMPTION, DIVIDEND, or RETURN_OF_CAPITAL cash flows depending on the distribution type. People confuse the two when building integrations.
When working with the calc service or reading calculation results, you're dealing with CashFlowTypes. When creating transactions via import or API, you're dealing with TransactionTypes. Keep a mapping reference handy and don't assume they match by name.
There's an asset-level toggle called overwriteCGWithReturns. When enabled, the "Capital Gains" metric on the dashboard is actually showing Total Returns instead. The label doesn't change -- it still says "Capital Gains" -- but the underlying calculation is different.
Clients may look at the dashboard, see "Capital Gains: $2.5M" and think that's actual capital gains, when it's really total returns including income distributions. This causes confusion in client conversations and reporting discrepancies.
Document which assets have this toggle enabled. When onboarding clients, explicitly ask whether they want "Capital Gains" to show actual capital gains or total returns. If the toggle is on, make sure everyone involved in reporting knows the label is misleading.
The Machine API (used for Delio Core integration) only supports ~7 of 17 transaction types: investor create/update, subscriptions, investments, capital calls, and asset show. It cannot create distributions, fees, fund cash movements, asset transfers, valuations, or adjustments.
This means you cannot fully automate the data pipeline from Delio Core to Portfolio. Some transaction types will always require manual XLSX import or direct REST API calls with a user session.
When designing an integration, map out which transaction types you need and check Machine API coverage first. For unsupported types, plan for either XLSX generation + upload via the bulk import API, or use the MCP API which has broader coverage (8 import tools). Don't promise full automation until you've verified coverage.
Before you can import subscriptions, distributions, capital calls, or cash pool transactions for an asset, the INVESTOR_SUBSCRIPTION_DISTRIBUTION toggle must be enabled on that asset. If it's not, every row referencing that asset fails with "The asset must have subscriptions/distributions enabled."
This toggle is separate from asset creation. You can create an asset via import, but if you don't enable this toggle before importing transactions, the transaction import fails. The toggle is not settable via the XLSX import -- it requires a UI action or API call.
After importing assets, enable the INVESTOR_SUBSCRIPTION_DISTRIBUTION toggle on each asset via the UI (Asset Settings) or the REST API before attempting any transaction imports. Build this step into your onboarding checklist.
Quick Reference: Gotchas at a Glance
| # | Gotcha | Impact | Prevention |
|---|---|---|---|
| 1 | Sheet ordering | Entire sheets fail to import | Follow dependency chain |
| 2 | BulkImportCache | Capital calls lose group mappings | Strict sheet order for groups/calls |
| 3 | Soft-deleted records | "Not found" for existing records | Check deleted_at before import |
| 4 | Heading row offset | Wrong row when debugging errors | Know your sheet's heading row |
| 5 | Valuations vs Replace | Duplicate errors on updates | Use Replace sheet for corrections |
| 6 | Subscription type scope | All investors get same type | Use child assets for different types |
| 7 | Transaction vs CashFlow types | Wrong enum in integrations | Keep mapping reference |
| 8 | overwriteCGWithReturns | Misleading dashboard labels | Document toggle state per asset |
| 9 | Machine API gaps | Can't automate all types | Check coverage before promising |
| 10 | Toggle prerequisites | Transaction imports rejected | Enable toggles after asset creation |
Knowledge Check
A client asks: "Can Portfolio replace our fund accounting system?" What's the most accurate answer?
An import fails with "Row 12. The investor field must exist." You check the database and find the investor record with matching client_reference. What's the most likely cause?
exists validation rules check deleted_at IS NULL. If the investor was soft-deleted, the record exists in the database but the validation treats it as non-existent. The fix is to either restore the record (clear deleted_at) or use a different client_reference. Option C is also a possible cause, but the question states you found the record in the DB -- so it was already created, meaning sheet order isn't the issue here.exists rules include deleted_at IS NULL, so a soft-deleted record is invisible to validation even though it's physically in the database.What's Next
You've now seen the honest picture -- what Portfolio does well and where to watch your step. The final module puts everything into practice with hands-on lab exercises where you'll build a fund from scratch, run a direct investment lifecycle, and debug common import failures.