Investor Portfolio
Show an investor their Wefunder holdings: a summary of totals and a paginated list of positions with per-security detail. Requires the read:investments scope on a user-authorized token — the investor grants your app access through the OAuth consent screen, and the endpoints return that investor's portfolio.
These endpoints are in beta. Field additions are possible; breaking changes will follow the deprecation policy.
Portfolio data is only available for the investor who authorized the token — an app cannot read another user's portfolio. App-only (client credentials) tokens receive 403.
Conventions
Monetary values are integer cents (215000 = $2,150.00) in the currency next to them (lower-case ISO 4217: usd, or eur for an ECSP round). Per-share prices and return multiples are decimal strings ("14.4632", "1.3454").
Summary totals are in one currency only — usd when the investor holds any USD position, otherwise the portfolio's only currency. Positions in any other currency are named in other_currencies and left out of the four total_*_cents figures (they still count in position_counts); read them, each with its own currency, from the positions endpoint.
Portfolio values are recalculated periodically rather than on each request, so recent investments and valuation changes can take a few minutes to appear. as_of is the timestamp of the oldest calculation included in the response.
current_value_cents equals cost_basis_cents until a later priced round, a recorded valuation mark, or an exit marks the position. Most active positions in a young portfolio read at cost, and that is the correct value, not missing data. In sandbox, the seeded investor's growth deal carries a 1.5× mark so you can see the field move; every other seeded position reads at cost.
Get portfolio summary
GET /portfolio
curl "https://api.wefunder.com/portfolio" \
-H "Authorization: Bearer USER_ACCESS_TOKEN"
{
"data": {
"type": "portfolio_summary",
"attributes": {
"currency": "usd",
"other_currencies": [],
"total_cost_basis_cents": 915000,
"total_current_value_cents": 1499264,
"total_unrealized_gain_cents": 264264,
"total_realized_gain_cents": 320000,
"return_multiple": "1.6385",
"position_counts": { "active": 2, "exited": 1, "sold": 0, "failed": 0 },
"as_of": "2026-07-26T03:15:00Z"
}
}
}
The summary accepts the same status and company query parameters as the positions endpoint, to total a slice of the portfolio — for example, ?status=exited for exited positions only.
List portfolio positions
GET /portfolio/positions
One position per offering (fundraise), newest first, cursor-paginated. The position sums everything the investor holds in that offering; securities breaks the total down.
curl "https://api.wefunder.com/portfolio/positions?per_page=25" \
-H "Authorization: Bearer USER_ACCESS_TOKEN"
{
"data": [
{
"id": "ofr_ozbvZ63cOy0CFao8Nv3SeEVU",
"type": "portfolio_position",
"attributes": {
"offering_id": "ofr_ozbvZ63cOy0CFao8Nv3SeEVU",
"company": {
"id": "co_2bt9eJn6X8C9gnShl63qlzkK",
"name": "Acme Robotics",
"slug": "acme-robotics",
"url": "https://wefunder.com/acme-robotics"
},
"asset_type": "company",
"security": { "type": "safe", "label": "SAFE" },
"status": "active",
"exit_reason": null,
"exited_on": null,
"invested_at": "2025-03-14T00:00:00Z",
"currency": "usd",
"cost_basis_cents": 215000,
"current_value_cents": 289264,
"unrealized_gain_cents": 74264,
"realized_gain_cents": 0,
"return_multiple": "1.3454",
"shares_held": "200.0",
"current_share_price": "14.4632",
"securities": [
{
"security": { "type": "safe", "label": "SAFE" },
"early_bird": true,
"terms": { "currency": "usd", "share_price": "10.0", "note_cap_cents": 800000000, "uncapped": false, "note_discount": null, "premoney_valuation_cents": null },
"cost_basis_cents": 100000,
"current_value_cents": 144632,
"unrealized_gain_cents": 44632,
"realized_gain_cents": 0,
"shares_held": "100.0",
"current_share_price": "14.4632",
"invested_at": "2025-03-14T00:00:00Z",
"owner": { "kind": "individual" }
},
{
"security": { "type": "safe", "label": "SAFE" },
"early_bird": false,
"terms": { "currency": "usd", "share_price": "12.0", "note_cap_cents": 1000000000, "uncapped": false, "note_discount": null, "premoney_valuation_cents": null },
"cost_basis_cents": 115000,
"current_value_cents": 144632,
"unrealized_gain_cents": 29632,
"realized_gain_cents": 0,
"shares_held": "100.0",
"current_share_price": "14.4632",
"invested_at": "2025-04-02T00:00:00Z",
"owner": { "kind": "entity", "name": "Blue Sky Holdings LLC" }
}
],
"holdings": [],
"as_of": "2026-07-27T14:02:11Z"
}
}
],
"meta": { "has_more": false, "page_count": 1 }
}
Query parameters
| Parameter | Type | Description |
|---|---|---|
cursor | integer | next_cursor from the previous page's meta. Omit for the first page. |
per_page | integer | Page size, 1–100. Defaults to 25. |
status | string | Filter by active, exited, sold, or failed. |
company | string | Filter to one company by its id (co_...). Unknown ids return 404. |
Reading securities
Each entry is one security offering held by one legal owner:
- Multiple tiers — a round that sold early bird tiers alongside regular terms produces one entry per tier, each with its own
terms(issue price, cap, discount). - Multiple legal owners — stakes held personally and stakes held through the investor's own entity (IRA, LLC) appear as separate entries, tagged
"owner": {"kind": "individual"}or{"kind": "entity", "name": "..."}. The position-level numbers already sum all entries.
terms follows the same rules as an offering's security.terms. An uncapped SAFE or convertible note has "uncapped": true and a null note_cap_cents; a capped one has "uncapped": false and the cap. A security with no discount, including one recorded as zero, has a null note_discount. uncapped is null for securities that never carry a cap (equity, revenue share, debt).
Linking to the company
company.url is the company's page on wefunder.com. Wherever you show a position, link it there: it is the link target the attribution rule asks for. The investor's Wefunder portfolio page is not a substitute, and you do not need to build the URL from slug.
Average cost per share
cost_basis_cents is in integer cents, while shares_held, current_share_price and terms.share_price are decimal strings in major units of the position's currency. An average cost per share that compares with those prices needs the cents converted first, using decimal arithmetic (not floats):
average_cost_per_share = decimal(cost_basis_cents) / 100 / decimal(shares_held)
For the position above: 215000 cents / "200.0" shares = 1075 cents per share = 10.75 USD per share, against a current_share_price of "14.4632". Dividing cost_basis_cents by shares_held without the / 100 gives cents per share — 100× the prices shown — so label it as cents if you keep it unconverted.
Skip the calculation when shares_held is null or zero. A revenue share or debt security, or a position whose latest valuation is not per-share, has no average cost per share: report it as unavailable rather than coercing the missing share count to a number or dividing by zero. The same applies per entry in securities, whose shares_held can be null even when the position-level value is not.
Funds, SPVs, and exits
asset_type: "fund"marks SPV and fund vehicles. The companies the vehicle invested in appear inholdings; some older vehicles have no recorded holdings and return an empty array.status: "sold"means the stake was transferred away in a secondary sale; proceeds show inrealized_gain_cents.exited_onis the date the position ended (YYYY-MM-DD), set withexit_reasononexitedandfailedpositions. Older exits recorded before Wefunder kept exit dates have anexit_reasonand a nullexited_on.exit_reason(ipo,acquisition,buyback,failed,winding_down) says why a position ended. It is set only forexitedandfailedpositions — asoldposition ended with the transfer itself — and some older positions have no recorded ending.ipomeans the company went public — it does not mean proceeds have been distributed.realized_gain_centsreflects actual distributions.shares_heldandcurrent_share_pricearenullwhen the investment is not share-denominated (revenue share, debt) or the latest valuation is not per-share;current_value_centsis always present.