Cookbook
Portfolio dashboard
Aggregate entities for a venture studio, fund admin, or holding company. One rollup read replaces dozens of per-entity calls.
Last updated
Aggregate entities for a venture studio, fund admin, or holding company. One rollup read replaces dozens of per-entity calls.
Trigger
You're running 20+ entities and want a single dashboard.
Call sequence
1. Create a portfolio
POST /v1/portfolios { name, metadata }2. Batch-form or attach the members
POST /v1/portfolios/{id}/entities/batch { entities: [...] }3. Roll up compliance
GET /v1/portfolios/{id}/complianceIdempotency
Both mutations idempotent via `Idempotency-Key`; re-attaching a member is a no-op by design.
Webhooks
| Event | Description |
|---|---|
portfolio.entity_added | Per entity attached to the portfolio. |
portfolio.batch_created | Once a batch fully resolves — every member registered or rejected. |
Errors
| Status | Code | Description |
|---|---|---|
404 | not_found | Portfolio or entity outside your org or mode — opaque by design. |
409 | state_conflict | Stale `expected_version` on update, or detaching from the wrong portfolio. |
The rollup shape
GET /v1/portfolios/{id}/compliance returns a portfolio_compliance_view, computed
on read:
{
"object": "portfolio_compliance_view",
"portfolio_id": "pf_S4dGqL2c",
"as_of": "2026-06-10T09:30:00Z",
"per_entity": [
{ "entity_id": "ent_Nq3KcAbc", "summary": { "upcoming": 2, "due_soon": 1, "overdue": 0, "completed": 4, "waived": 0 } },
{ "entity_id": "ent_Pk7RwXyZt", "summary": { "upcoming": 1, "due_soon": 0, "overdue": 1, "completed": 3, "waived": 1 } }
],
"totals": { "entities": 2, "upcoming": 3, "due_soon": 1, "overdue": 1, "completed": 7, "waived": 1 },
"critical_actions": [
{ "id": "co_h2k9m4qx7w1rt3n6", "entity_id": "ent_Pk7RwXyZt", "kind": "franchise_tax", "jurisdiction": "US-DE", "due_at": "2026-06-01T00:00:00Z", "status": "overdue" }
]
}critical_actions is the triage queue — every overdue obligation first, then the
soonest due-soon items, capped at 10. A dashboard that renders totals plus
critical_actions covers the morning check without touching any per-entity
endpoint.
Variations
Drive the day's work straight off critical_actions — no client-side
filtering required:
const view = await getPortfolioCompliance("pf_Studio01");
for (const action of view.critical_actions) {
if (action.status === "overdue") {
await notifyOperator(action.entity_id, action.kind, action.due_at);
}
}Wound-down or transferred companies shouldn't show up here at all — detach
them via DELETE /v1/portfolios/{id}/entities/{entity_id} and the rollup
stops counting them on the next read.
For a flat list of every member with full envelopes, paginate
GET /v1/portfolios/{id}/entities with cursor:
async function* streamEntities(portfolioId: string) {
let cursor: string | undefined;
do {
const url = new URL(
`https://api.mattermode.com/v1/portfolios/${portfolioId}/entities`
);
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("starting_after", cursor);
const res = await fetch(url, {
headers: {
"Authorization": `Bearer ${process.env.MATTER_KEY}`,
"Matter-Version": "2026-05-01",
},
});
const page = await res.json();
for (const entity of page.data) yield entity;
cursor = page.has_more ? page.next_cursor : undefined;
} while (cursor);
}For ids alone, skip the pagination — GET /v1/portfolios/{id} carries the
full entity_ids array on the portfolio itself.
For an audit trail of how the portfolio evolved, snapshot the rollup daily:
import fs from "node:fs/promises";
const today = new Date().toISOString().split("T")[0];
const view = await getPortfolioCompliance("pf_Studio01");
await fs.writeFile(
`./snapshots/${today}.json`,
JSON.stringify(view, null, 2)
);Diff totals and critical_actions against yesterday for a "what changed"
report. The as_of timestamp on each snapshot records exactly when the view
was computed.
Many dashboards, one org
Running one portfolio per fund, cohort, or customer? GET /v1/portfolios
lists every portfolio in your org, newest first, each row carrying its member
entity_ids — one read surfaces every grouping. For embedded deployments,
set metadata.customer_id at creation and correlate each portfolio back to
your own customer records; pair with a
portfolio-scoped token so each
customer's key only sees its own dashboard. Per-portfolio webhook endpoints —
a portfolio_id filter so each customer's events deliver separately — ship
next.
Related
- Portfolio resource — full schema
- Compliance view — per-entity equivalent
- File all due — the write-side companion
- Mint a portfolio-scoped token — delegate one portfolio
- Webhooks — subscription model and delivery guarantees