EPD Schema (API)
Once an EPD has been created, the schema endpoint tells you exactly which fields it expects, which requirements will be checked against it, and what constraints apply. Use it to drive dynamic forms, build validation in your own integrations, or simply discover what a given EPD's spec looks like.
This page is the field-by-field reference for the response. For the concepts behind templates, locked fields, and constraints, see EPD Templating and Specs and Validation.
Endpoint
GET /api/v2/epds/{epd_id}/schema/?view={full|summary}
| Query param | Default | Effect |
|---|---|---|
view=full | ✅ | Returns the verbose internal shape. Stable across releases but carries inheritance metadata, legacy duals, and resolver-internal flags. |
view=summary | Returns a slim, integrator-friendly shape. Recommended for new integrations and dynamic form generation. |
The endpoint walks the EPD's pinned spec inheritance chain (PCR → standard → category-specific overrides) and emits the fully resolved set of input fields and requirements. Requirements are returned without their internal checks — those are evaluated server-side by the validation engine.
The schema only ever contains fields and requirements that are live for this EPD's spec. When a PCR override removes an inherited field or requirement, it does not appear in either view — there is no _removed flag in the public response. (Internally the resolver marks removed items so the spec editor can render them struck-through, but that marker is never exposed here.) Treat the schema as authoritative: anything present is collected; anything removed is simply absent.
Two views at a glance
view=full | view=summary | |
|---|---|---|
| Top-level keys | input_field_defs, requirements, indicator_catalog | fields, requirements, counts |
| Field key style | field_id, is_required, group_id, show_if_json | id, required, group, show_if |
| Inheritance metadata | Included (_inherited, _inherited_from, override_mode) | Stripped |
| Legacy duals | Included (constraints_json, scalar cross_ref_id) | Stripped (only normalized form remains) |
| Best for | Internal tooling, debugging the resolver | API integrators, form builders, third-party validators |
If you're starting fresh, use ?view=summary. The full view exists for backwards compatibility and parity with the resolver's internal shape.
Summary view reference
curl -H "Authorization: Bearer $TOKEN" \
"https://sandbox.smartepd.xyz/api/v2/epds/$EPD_ID/schema/?view=summary"
Response shape:
{
"fields": [ /* EpdSchemaSummaryFieldOut */ ],
"requirements": [ /* EpdSchemaSummaryRequirementOut */ ],
"counts": { "fields": 47, "requirements": 23 }
}
fields[]
Each field describes one input on the EPD form.
| Key | Type | Meaning |
|---|---|---|
id | string | Stable field identifier. Use this as the document key when filling the EPD (e.g., product-name, a1_a3_gwp). |
type | string | Input type: text, number, date, select, multiselect, richtext, table, plant_picker, ec3_category, etc. Drives which form widget to render. |
label | string | Human-readable label for the form field. |
help_text | string | Hint shown beneath the input. May be empty. |
group | string | null | The id of the field group this field belongs to, or null if ungrouped. Use to render related fields together. |
order | int | Display order within the group (or globally, if ungrouped). Sort ascending. |
required | bool | If true, the EPD will fail submission unless this field has a value. |
locked | bool | If true, the value is prefilled by the template and cannot be edited. Render as read-only and use locked_value for the displayed value. |
locked_value | any | null | The prefilled value when locked is true. Type matches type (string, number, list, etc.). |
options | list | null | For select and multiselect types: the available choices, each as {label, value, ...}. null for free-text fields. |
constraints | list | Validation rules attached to this field. See Constraint shapes. |
show_if | object | null | Conditional visibility rule. The field only renders when the rule evaluates to true against the current document values. See Conditional logic. |
default | any | null | Default value to seed the input with on first render. |
requirements[]
Requirements are compliance clauses (typically from the PCR) that the EPD must satisfy. The schema endpoint exposes them so integrators can present them alongside the form, but the actual evaluation happens server-side when the EPD is submitted (/epds/{id}/submit/) or validated (/epds/{id}/validate/).
| Key | Type | Meaning |
|---|---|---|
id | string | Stable requirement identifier (e.g., EN15804_4.1). |
title | string | Short heading. |
text | string | Full requirement text — show this to verifiers and manufacturers. |
topic | string | Logical grouping (e.g., lifecycle_stages, declared_unit). Use to organize the verifier sidebar. |
comment_type | string | One of mandatory, guidance, info. Drives severity in the UI. |
applies_to | list<string> | Document types this requirement applies to: usually ["epd"], sometimes ["lca_report"] or ["pcr"]. |
refs | list<string> | The field.id values this requirement cross-references. Use to link a requirement back to the form fields it governs. |
applies_if | object | null | Conditional applicability — a document-text search gate ({"document_type", "query", "mode"} plus an optional boolean negate; modes: all_phrases, any_phrases, any_topics, regex), evaluated against the submitted documents. Not the show_if shape — see Conditional logic. |
counts
A small object with {"fields": N, "requirements": M}. Useful for quick sanity checks ("does this template have the expected number of fields?") without iterating the lists.
Constraint shapes
Each item in field.constraints has this shape:
{
"constraint_type": "range",
"parameters_json": { "min": 0, "max": 1000, "bounds_mode": "inclusive" },
"severity": "error",
"applies_if_json": null
}
| Key | Meaning |
|---|---|
constraint_type | One of range, statistical, formula, allowed_values. |
parameters_json | Type-specific params — see EPD Templating for full details. Keys per type: min/max/bounds_mode (range); distribution/threshold plus mean/std_dev for normal or geometric_mean/geometric_std_dev for lognormal (statistical); expression/operator with threshold, or min/max for the between operator (formula); values/case_sensitive (allowed_values). |
severity | error blocks variant approve/auto-approve, and blocks a reference EPD's submission when the template has constraints_enforced on unless the author acknowledges the failure. warning and info are advisory. |
applies_if_json | Optional conditional gate — {field, operator, value} leaves with all/any combinators. See Conditional logic below. Not the show_if shape. |
Spec-level constraints anchor implicitly to the field that declares them. Template-level constraints (the template's template_constraints list) additionally carry a required reference_field_id — the anchor field that violations are attributed to — and a display label.
Conditional logic
Three different conditional gates exist in the schema, and they do not share a shape — don't copy snippets between them.
show_if (field visibility) puts the operator in the key:
{ "field": "product-form", "eq": "powder" }
Leaf operators: eq, ne, and contains (list membership, for multiselect fields). Compose with any / all:
{
"all": [
{ "field": "country", "eq": "US" },
{ "field": "applicable-in", "contains": "EU" }
]
}
A null or non-object condition, and any unknown leaf operator, default to visible. Combinator values must be arrays of condition objects — other shapes are outside the contract. (Note that {"any": []} is well-formed and evaluates false: no branch matched, so the field is hidden.) Evaluate against the in-flight document values to decide whether to render the field; the server applies the same rules at submission time (hidden fields are not validated), so client-side evaluation is purely for UX.
Constraint applies_if_json uses {field, operator, value} leaves with operators eq, neq, in, gt, gte, lt, lte, present, absent, composed with all / any — see the conditional application grammar.
Requirement applies_if is a document-text search (document_type + query + mode), unrelated to either of the above.
Groups
Field groups aren't returned as a separate top-level list in either view (they live in the spec, not the schema response). To render grouped forms, bucket fields by group yourself:
curl -H "Authorization: Bearer $TOKEN" \
"https://sandbox.smartepd.xyz/api/v2/epds/$EPD_ID/schema/?view=summary" \
| jq '.fields | group_by(.group) | map({group: .[0].group, fields: map(.id)})'
If you need group labels and ordering, fetch the spec version directly via GET /api/v2/specs/{spec_id}/versions/{version}/, which exposes field_groups[] with id, label, description, order, and nested parent_group.
Full view reference
The full view is the verbose default. It carries everything the summary does, plus the keys in the table below. Most integrations should prefer ?view=summary; reach for view=full only when you need the resolver's internal state (e.g., debugging an inheritance override).
Extra keys on each input_field_defs[] entry
| Key | Meaning |
|---|---|
field_id | Same as summary id. |
is_required | Same as summary required. |
group_id | Same as summary group. |
placeholder | UI placeholder for the input. |
default_value | Same as summary default. |
options_json | Same as summary options. |
show_if_json | Same as summary show_if. |
constraints_json | Legacy. Inline constraint blob; superseded by the normalized constraints[] list. Prefer constraints[]. |
view_hidden | If true, the field is collected during data entry but suppressed from the digital EPD view. |
_inherited | true if the field came from a parent spec rather than the leaf spec. |
_inherited_from | Display name of the parent spec, if inherited. |
override_mode | How an inherited field was modified by a child spec: replace, merge, etc. |
indicator_catalog
Full view only. The slice of the LCIA indicator catalog this EPD's catalog-mode
results table may draw rows from, or null when its spec declares no such
table. Shaped {catalog_version, methods[], entries[]} — the same payload
GET /api/v2/specs/indicator-catalog/ returns, restricted to the methods the
table permits.
Because the summary view omits this key, integrations on ?view=summary should
read the catalog from the standalone endpoint. See
Writing Results Data for the
entry shape and how the ids are used.
Extra keys on each requirements[] entry
| Key | Meaning |
|---|---|
requirement_id | Same as summary id. |
document_types | Same as summary applies_to. |
cross_ref_ids | Same as summary refs. |
cross_ref_id | Legacy. Single scalar cross-reference held during the migration to cross_ref_ids[]. Prefer the list. |
output_targets | Where the requirement appears in generated output (e.g., ["pdf", "digital"]). |
sources_json | Citations and external references (e.g., PCR section numbers). |
compliance_requirement | shall, should, or may. |
applies_if_json | Same as summary applies_if. |
inherited | true if the requirement came from a parent spec. |
Worked example: render a form from the schema
This jq snippet pulls the summary view and prints a flat outline grouped by section, with required fields marked:
curl -s -H "Authorization: Bearer $TOKEN" \
"https://sandbox.smartepd.xyz/api/v2/epds/$EPD_ID/schema/?view=summary" \
| jq -r '
.fields
| group_by(.group // "ungrouped")
| .[]
| "## \(.[0].group // "Ungrouped")\n" +
(map(
"- \(.id) [\(.type)]" +
(if .required then " *required*" else "" end) +
(if .locked then " (locked: \(.locked_value | tojson))" else "" end) +
(if (.constraints | length) > 0 then " — \(.constraints | length) constraint(s)" else "" end)
) | join("\n"))
'
For a richer integration, walk fields to build form widgets keyed by type, evaluate each show_if against the current document state, and render constraints inline as helper text or client-side validators. Then submit the populated document to PUT /api/v2/epds/{epd_id}/document/ — the server re-evaluates everything authoritatively at POST /api/v2/epds/{epd_id}/submit/.
Related
- EPD Templating — what locked, unlocked, and constrained fields mean in the verification model.
- Specs and Validation — how requirements, fields, and the inheritance chain are authored upstream.
- API Quickstart, step 6 — the end-to-end walkthrough where this endpoint first appears.