Skip to main content

Writing Results Data

Environmental results (impact assessment, resource use, output flows, carbon flows) are written as part of the EPD document via:

PUT /api/v2/epds/{epd_id}/document/

This page documents the shape of that results data — the one part of the document whose keys are validated against the EPD's PCR spec rather than free-form.

Where results live​

Each results table is a top-level document key holding a list of result sets. Most EPDs have exactly one set per table:

Document keyTableRows come from
impactsEnvironmental Impact Assessmentthe PCR spec
resource_usesResource Use Indicatorsthe PCR spec
output_flowsWaste and Output Flow Indicatorsthe PCR spec
carbon_flowsCarbon Emissions and Removalsthe PCR spec
additional_resultsAdditional Indicators (optional)the indicator catalog
Field id and document key differ here

/schema/ returns the field id (resource-uses, hyphens); the document key these tables are bound to is resource_uses (underscores). Only impacts is spelled the same both ways, which makes the difference easy to miss.

Either spelling is accepted on write, and validation reads both (it tries the bound key, then falls back to the field id). What differs is what comes back:

EndpointReturns
GET /epds/{id}/your document verbatim — exactly the keys you sent
GET /epds/{id}/view/the same, plus the bound-key alias

So a table written as resource-uses reads back as resource-uses from the detail endpoint, not resource_uses — it is not missing. Pick one spelling and use it consistently; the bound key is the better choice, because it is the one both endpoints agree on. The spec's bindings_json is the mapping.

Each set is an object with a results-data matrix and an optional discussion (HTML string):

{
"impacts": [
{
"discussion": "<p>Cradle-to-gate impacts are dominated by module A1.</p>",
"results-data": {
"GWP-total": { "a1a2a3": 173.83, "a4": 3.94, "c2": 1.99, "d": -90.19 },
"ODP": { "a1a2a3": 0.00000865, "a4": 0.00000007, "d": -0.00000417 }
}
}
]
}

The results-data matrix​

results-data maps indicator id → module key → value.

  • Indicator ids (rows) must be indicators declared by the EPD's PCR spec (on the Additional Indicators table they come from the catalog instead) — fetch them from GET /api/v2/epds/{epd_id}/schema/ (the results_matrix item field inside each results section_list lists indicators with id, label, and unit). Typical EN 15804+A2 ids: GWP-total, GWP-fossil, GWP-biogenic, GWP-luluc, ODP, AP, EP-freshwater, EP-marine, EP-terrestrial, POCP, ADP-minerals&metals, ADP-fossil, WDP, PM, IRP, ETP-fw, HTP-c, HTP-nc, SQP.
  • Module keys (columns) are lifecycle module codes: a1, a2, a3, a4, a5, b1–b7, c1–c4, d — plus the aggregated production column a1a2a3 (A1-A3 combined), which is always accepted.
  • Values may be JSON numbers or numeric strings (scientific notation is fine). Units are not written per value — each indicator's unit comes from the PCR spec and renders automatically.

Key casing​

Canonical module keys are lowercase (a1a2a3, a4), and indicator ids use the spec's exact spelling (GWP-total). The API is tolerant on write: any casing is accepted (A1A2A3, gwp-total) and the server canonicalizes keys before storing, so integrations that already send uppercase module keys keep working unchanged. What you read back via GET is the canonical form.

What gets rejected​

The PCR spec is the contract for this matrix, so two things fail the whole document write with 400 results_matrix_invalid (violation details included per key):

  • an indicator id the spec doesn't declare (unknown_indicator), and
  • a module key that is neither a module declared in information_modules nor a1a2a3 (unknown_module).

Two other things also fail the write: a malformed matrix or reserved map (results_matrix_shape), and, on a variant EPD, a value that drifts from a field its reference EPD locked. Everything else about the document is validated softly — the save succeeds and issues are reported in constraint_violations on the EPD detail.

Declared modules and production display​

Two sibling document fields control which columns exist and how the production stage displays:

  • information_modules — the declared lifecycle modules, as a lowercase list, e.g. ["a1","a2","a3","a4","a5","c2","c3","c4","d"]. This is what makes a module a legal column key.
  • production_stage_show_as — how the production stage displays; see the next section.

If the display setting and the data disagree (e.g. "separated" declared but only a1a2a3 reported), nothing is lost — the editor and digital view render every column that actually holds data — but keeping them consistent renders the cleanest tables.

Production stage display​

production_stage_show_as decides how modules A1, A2 and A3 appear in every results table, the PCR tables and the Additional Indicators table alike:

ValueColumns
aggregated (default)one a1a2a3 column, shown as A1-A3
separateda1, a2, a3
botha1, a2, a3, then a1a2a3 (the aggregate column follows A3 everywhere: editor, view, PDF, workbook)

The document may hold both kinds of keys whatever the display says; validation accepts a1a2a3 alongside the declared modules. The grids never hide a column that already holds a value, so switching from aggregated to separated keeps a populated A1-A3 column visible. Under aggregated and separated the print document additionally drops A1, A2 or A3 columns that are empty beside a populated aggregate; under both all four columns print.

Optional: label overrides​

A reserved _labels key inside results-data renames indicators for display (e.g. translations) without changing their ids:

"results-data": {
"GWP-total": { "a1a2a3": 173.83 },
"_labels": { "GWP-total": "Climate change (total)" }
}

Keys inside _labels must be declared indicator ids (unknown_label_override rejects otherwise).

How PCR table units and methods are resolved​

Every row of the four PCR tables is tied to the LCIA indicator catalog through a catalog_id on the spec's indicator definition. Inventory flows carry one id (FW → fw) because they mean the same thing under every reporting convention. Impact rows whose characterization method follows the route carry one id per results-convention value:

{ "id": "AP", "unit": "mol H+ eq", "label": "AP",
"catalog_id": { "en15804a2": "ap@ef-3.1", "iso21930": "ap@traci-2.2" } }

The document never changes: rows stay keyed by the spec id (AP). At render time the digital view, the PDF and the editor grid look up the entry for the EPD's results_convention (falling back to the spec's default) and take the unit and methodology from the catalog, so an ISO 21930 EPD shows AP in kg SO2 eq under TRACI 2.2 while an EN 15804+A2 EPD shows mol H+ eq under EF 3.1. A row with no entry for the current convention keeps the spec's own unit and shows no method. The spec's unit is that fallback; spec_catalog_drift() reports where it disagrees with the catalog.

A PCR that replaces a table through an amend_field override (NSF 1103 swaps in its own impact rows — GWP-total and GWP-biogenic under IPCC AR6, the other categories under TRACI 2.1 — and locks the convention to ISO 21930) ties those rows in its own seed JSON; a locked convention always wins over the document value. A row may also name its methodology outright with lca_method (NSF 1103's GWP-20, which has no catalog entry); that wins over the catalog's.

The Additional Indicators table (catalog mode)​

Specs derived from the base EPD input spec declare a fifth, optional results table — additional-results (document key additional_results) — for indicators the PCR does not prescribe. Its results_matrix runs in catalog mode (indicator_source: "catalog" in the schema): rows are picked from the normalized LCIA indicator catalog rather than typed. A row key is a catalog id, <indicator>@<method>, and the label, unit and methodology are resolved from the catalog when the EPD renders — so a unit is a property of the (method, indicator) pair, never something you send.

{
"additional_results": [
{
"discussion": "<p>Reported per the program operator's regional method.</p>",
"results-data": {
"ap@traci-2.2": { "a1a2a3": 0.0021, "a4": 0.00004 },
"wdp@ef-3.1": { "a1a2a3": 12.4 }
}
}
]
}
  • Which ids are legal: any entry of the catalog whose method is in the table's catalog_methods, plus every method-less flow (pere, hwd, …), which are always legal. Anything else is unknown_indicator and fails the write. See Reading the catalog for how to fetch the legal set.
  • No _labels / _units for catalog rows: the catalog owns both; sending them for a catalog id is unknown_label_override / unknown_unit_override. The two maps still work for indicators the spec itself declares on the table.
  • Columns stay locked: unknown_module applies exactly as on the PCR tables.
  • The catalog is versioned; entries are never deleted, so an id you store keeps resolving. Entries marked status: "provisional" are usable — only their unit is still under methodology review, and a corrected unit shows on every EPD automatically because units are never stored per document.

An entry marked selectable: false in the catalog (today, the TRACI 2.1 freshwater/marine eutrophication aliases) still resolves for rows that already use it, but should not be offered for new rows — treat it as read-only in your own pickers.

Reading the catalog​

Two endpoints serve the catalog. Prefer the standalone one: it is cacheable, independent of any EPD, and the only one on the summary schema path.

GET /api/v2/specs/indicator-catalog/ # whole catalog
GET /api/v2/specs/indicator-catalog/?methods=traci-2.2,ef-3.1 # filtered slice
{
"catalog_version": "1.0.0",
"methods": [
{ "id": "traci-2.2", "name": "TRACI 2.2", "ec3_method": "TRACI 2.2" }
],
"entries": [
{
"id": "ap@traci-2.2",
"method": "TRACI 2.2",
"method_id": "traci-2.2",
"indicator": "AP",
"label": "Acidification Potential",
"unit": "kg SO2 eq",
"family": "impacts",
"status": "verified",
"ec3": { "method": "TRACI 2.2", "key": "ap", "unit": "kgSO2e" }
},
{
"id": "pere",
"method": null,
"method_id": null,
"indicator": "PERE",
"label": "PERE",
"unit": "MJ",
"family": "resources",
"status": "verified",
"standard": "en15804",
"aliases": ["Renewable primary energy — energy use"],
"ec3": { "method": null, "key": null, "unit": "MJ" }
}
]
}
Entry keyMeaning
idThe row key you write into results-data. <indicator>@<method> for an impact, a bare slug for a method-less flow.
method / method_idDisplay name and id of the characterization method; both null for inventory flows.
indicator / labelShort code and display name. Neither is a valid row key — always write id.
unitResolved at render time. Never send it; _units on a catalog row is rejected.
familyimpacts, resources, waste or carbon — useful for grouping a picker.
statusverified, or provisional while a unit is still under methodology review. Provisional entries are fully usable.
selectablePresent and false only on entries kept for existing data. Do not offer these for new rows.
aliasesAlternative names, for matching rows imported from elsewhere.
ec3The corresponding EC3 method, key and unit spelling, when one exists.

methods[] gives you the picker's first level; filter entries[] by method_id for the second. Filtering the request by ?methods= never removes the method-less flows — they belong to every slice.

The per-EPD alternative is the full schema view, which carries the slice a given EPD's table permits:

GET /api/v2/epds/{epd_id}/schema/ # full view: includes `indicator_catalog`
GET /api/v2/epds/{epd_id}/schema/?view=summary # does NOT include it

indicator_catalog is null when the EPD's spec declares no catalog-mode table. Because the summary view omits it, an integration built on ?view=summary (the view these docs otherwise recommend) should read the catalog from the standalone endpoint.

Free mode (legacy)​

A results_matrix may instead declare indicator_source: "free" (or the legacy allow_custom_indicators: true). On such a table you define the rows: any id not starting with _ is accepted, and the reserved _units map ({indicator id: unit string}, the sibling of _labels) is the only source of a unit for an indicator the spec does not declare. Ids starting with _ are rejected (results_matrix_shape). On a spec-locked table (indicator_source absent or "spec") _units keys must be declared indicators, mirroring _labels, and the spec's unit always wins for a declared indicator.

Both modes render after the PCR tables in the digital EPD and the PDF, and round-trip through the project XLSX export/import (the lateral editor's workbook). The editor's own "Download/Upload All Results" template covers the four PCR tables only.