Skip to Content
Portfolios

Portfolios

A portfolio is a group of companies that one company, the portfolio’s owner, reports on together, such as a parent company and its subsidiaries. Each member has an inclusion share: how much of its emissions count towards the portfolio.

Both operations need the portfolio:read scope, and take the owner’s company id in the path.

List portfolios

GET /v3/company/{companyId}/portfolios returns every portfolio the company owns, oldest first, with its member companies: the parent first, then by name.

curl --max-time 60 "https://api.energi.ai/v3/company/1042/portfolios" \ -H "Authorization: Bearer $ENERGI_API_KEY"
curl --max-time 60 "https://api.us.energi.ai/v3/company/1042/portfolios" \ -H "Authorization: Bearer $ENERGI_API_KEY"
{ "data": [ { "id": "189", "name": "Energi.AI Group", "companies": [ { "companyId": "1042", "name": "Energi.AI AS", "orgNumber": "927611074", "inclusionPercent": 100, "dataSharingConsent": "ACTIVE" }, { "companyId": "2311", "name": "Example Subsidiary AS", "orgNumber": "900000001", "inclusionPercent": 60, "dataSharingConsent": "MISSING" } ] } ], "meta": { "nextCursor": null } }
  • inclusionPercent is the share of the member’s emissions included in the portfolio, from 0 to 100.
  • dataSharingConsent says whether the member has granted the owner data-sharing consent: ACTIVE or MISSING. The owner itself is always ACTIVE. Members with MISSING are left out of the portfolio’s emissions, so this list tells you in advance who will be.
  • meta.nextCursor is always null today: the list isn’t paginated yet (see Lists).

Read a portfolio’s emissions

GET /v3/company/{companyId}/portfolios/{portfolioId}/emissions/summary returns the portfolio’s total emissions and each included member’s contribution, on the same grid of periods, scopes and categories as a company summary. It takes the same from, to, granularity and emissionsUnit, with the same rules, plus a required reportType.

Report types

reportTypeMeaning
PORTFOLIOEach member’s emissions, multiplied by its inclusionPercent, added up.
CONSOLIDATIONThe same, without intra-group transactions: data points whose vendor’s organisation number belongs to any company in the portfolio are left out, in every direction. This avoids counting the same emissions twice when members buy from each other.
curl --max-time 60 -G "https://api.energi.ai/v3/company/1042/portfolios/189/emissions/summary" \ -H "Authorization: Bearer $ENERGI_API_KEY" \ -d from=2025-01 -d to=2025-12 -d granularity=QUARTER -d emissionsUnit=t \ -d reportType=CONSOLIDATION
curl --max-time 60 -G "https://api.us.energi.ai/v3/company/1042/portfolios/189/emissions/summary" \ -H "Authorization: Bearer $ENERGI_API_KEY" \ -d from=2025-01 -d to=2025-12 -d granularity=QUARTER -d emissionsUnit=t \ -d reportType=CONSOLIDATION

The response, with periods shortened:

{ "data": { "companyId": "1042", "portfolio": { "id": "189", "name": "Energi.AI Group" }, "reportType": "CONSOLIDATION", "emissionsUnit": "tCO2e", "granularity": "QUARTER", "from": "2025-01", "to": "2025-12", "emissions": 4821.55, "periods": [], "companies": [ { "companyId": "1042", "name": "Energi.AI AS", "orgNumber": "927611074", "inclusionPercent": 100, "emissions": 4821.55, "periods": [] } ], "excludedCompanies": [ { "companyId": "2311", "reason": "NO_DATA_SHARING_CONSENT" } ] }, "meta": { "status": "UPDATING", "asOf": "2026-09-16T09:12:04.000Z" } }
  • data.periods has the same shape as in a company summary, for the portfolio as a whole. In a real response it is never empty.
  • companies lists every included member, the parent first, then by name. Each one’s emissions and periods are its contribution, after inclusionPercent.
  • excludedCompanies lists the members left out of every figure, with the reason. Today the one reason is NO_DATA_SHARING_CONSENT; accept others you don’t know. Both lists are [] when empty.
  • A portfolio without members returns the grid with every value null.
  • A portfolio id that doesn’t exist, or belongs to another company, is a 404 NOT_FOUND.

Freshness

meta works as for a company summary, across all included members: status is UPDATING if any of them is still being calculated, and asOf is then the oldest asOf among those. A member that isn’t updating reflects every change, so it never holds asOf back. When none is updating, asOf is the newest among them.

Last updated on