Public EPD Library
One anonymous endpoint lists every publicly listed EPD on Smart EPD, from both platforms:
v2, the current platform.v1, the classic platform.
It needs no API key, no login and no headers. Use it to power a product catalogue, sync the library into your own database, or look up whether a manufacturer's declaration is still valid.
curl -s "https://app.smartepd.com/api/v2/public/epds/?q=cement&limit=5" | jq
Smart EPD is migrating from its classic platform to the current one, so today a published EPD lives on one or the other. This endpoint merges them into a single newest-first list. Every row says which platform it came from, and links to that platform's public page for the EPD.
What is listed
An EPD is listed exactly when its own platform shows it publicly. That's the same rule its public web page applies, so a listed EPD's url always resolves.
| Rule | Current platform (v2) | Classic platform (v1) |
|---|---|---|
| Published | Status is published | Status is published and the EPD is marked visible in the library |
| Not expired | Expiration date is today or later, or none is recorded | Same |
| Not archived | Archived EPDs are never listed | Same |
- Expiry. An EPD stays listed through its expiration date and drops out the following day.
- Republished EPDs. A superseded version is not listed; only the current version is.
- Same product on both platforms. A product can appear twice if it was re-declared on the current platform while its classic EPD is still published. These are separate declarations with separate declaration numbers.
Request
GET /api/v2/public/epds/
| Query parameter | Default | Description |
|---|---|---|
q | — | Case-insensitive search across product name, manufacturer name and declaration number. Hyphens and spaces match each other, so smartepd 2025 finds SmartEPD-2025-…. Maximum 200 characters. |
platform | both | v1 or v2 to list one platform only. Any other value returns 422. |
limit | 25 | Page size, clamped to 1–100. |
cursor | — | The next_cursor from the previous page. |
Requests are rate limited to 300 per minute per client IP, which is plenty to walk the whole library (roughly ten requests at limit=100). Past that you get 429 with error code rate_limited: back off and retry. The v2 detail endpoint below has its own, lower limit of 120 per minute per client IP.
Response
{
"data": [
{
"id": "01928f3a-7c1e-7b4d-9a2e-5f6b8c9d0e1f",
"platform": "v2",
"product_name": "Type X Gypsum Board",
"manufacturer": "Acme Gypsum",
"declaration_number": "SmartEPD-2026-012-0042-01",
"pcr": { "name": "PCR for Gypsum Board", "version": "2.0" },
"date_of_issue": "2026-09-24",
"expiration_date": "2031-09-24",
"is_prospective": false,
"ec3_id": null,
"url": "https://app.smartepd.com/v2/epd-library/01928f3a-7c1e-7b4d-9a2e-5f6b8c9d0e1f",
"detail_url": "https://app.smartepd.com/api/v2/public/epds/01928f3a-7c1e-7b4d-9a2e-5f6b8c9d0e1f/view/"
},
{
"id": "65f0aabbccddeeff00112233",
"platform": "v1",
"product_name": "Series 100 Epoxy Coating",
"manufacturer": "Acme Coatings",
"declaration_number": "SmartEPD-2026-151-1015-02",
"pcr": { "name": "PCR for Architectural Coatings", "version": "1" },
"date_of_issue": "2026-09-23",
"expiration_date": "2031-09-23",
"is_prospective": false,
"ec3_id": "ec3a1b2c3d4e5f60718293a4b5c6d7e8",
"url": "https://app.smartepd.com/epd-library/65f0aabbccddeeff00112233",
"detail_url": "https://app.smartepd.com/api/epd-library/65f0aabbccddeeff00112233"
}
],
"next_cursor": "bTp7InYxIjogWyIyMDI2LTA5LTIzVDE0OjAyOjExIiwgIjY4ZjYz...",
"has_more": true,
"unavailable_platforms": []
}
| Field | Description |
|---|---|
id | The EPD's id on its own platform: a UUID for v2, a 24-character hex id for v1. Key your records on the pair (platform, id), because id alone is only unique within one platform. |
platform | v2 or v1. |
product_name, manufacturer | Product and declaring organization. manufacturer may be null. |
declaration_number | The registration number printed on the EPD. |
pcr | The Product Category Rules the EPD conforms to (name, and version where the PCR publishes one), or null. |
date_of_issue, expiration_date | YYYY-MM-DD. date_of_issue can be null on older EPDs that never recorded it. |
is_prospective | true for a prospective EPD, which is based on a forward-looking LCA and valid for one year. |
ec3_id | The EPD's id in Building Transparency's EC3, when it is listed there. |
url | The EPD's public web page. Link people here. |
detail_url | Anonymous JSON for the full EPD. |
Fetching the full EPD
detail_url points at each platform's own detail endpoint, and the two payloads have different shapes:
v2:GET /api/v2/public/epds/{id}/view/(documented in the API Reference) returns the complete digital declaration: document, results, PCR metadata and verifier.v1: the classic platform's library record. It is a V1-shaped payload that the v2 OpenAPI spec does not describe.
Everything you need to list, search and link EPDs is in the summary rows above. Only follow detail_url when you need the full declaration.
Ordering and pagination
The list is newest first by date of issue, across both platforms. It uses the standard cursor envelope (data / next_cursor / has_more), so walk it until next_cursor is null:
CURSOR=""
while :; do
PAGE=$(curl -s "https://app.smartepd.com/api/v2/public/epds/?limit=100${CURSOR:+&cursor=$CURSOR}")
echo "$PAGE" | jq -c '.data[] | {platform, id, product_name}'
CURSOR=$(echo "$PAGE" | jq -r '.next_cursor // empty')
[ -z "$CURSOR" ] && break
done
- Gapless. Unlike the
created_atcaveat on other list endpoints, this cursor never skips or repeats an EPD, even when many share a date of issue. It keeps a separate position for each platform, so each page resumes both exactly where they stopped. - Opaque. Pass it back unchanged. It is only valid for this endpoint. A malformed cursor returns the first page rather than an error.
- Tied to your query. Keep
qandplatformthe same for the whole walk. A cursor carried over to a different query won't fail, but that query's results can come back out of order. - No total count. There isn't one. Walk the pages if you need the number.
When a platform can't be read
unavailable_platforms is normally []. If one platform can't be reached, for example ["v1"] during a classic-platform outage:
- The page is served from the other platform only.
has_morereflects only the platform that was read.
Don't treat EPDs missing from that page as delisted. If you mirror the library, skip deletions for any run where unavailable_platforms was non-empty and retry later. You can keep paginating, because the cursor holds the unavailable platform's place. When it comes back, later pages pick it up exactly where it left off, although those rows may be older than ones you have already received.
After an outage, Smart EPD waits about 30 seconds before trying the unavailable platform again, so unavailable_platforms can stay set briefly after the platform has recovered.
Regional deployments
The EU deployment (https://app.smartepd.eu) serves the same endpoint for the EU library. The classic platform never ran there, so every EU row is v2 and unavailable_platforms is always []. The two regions are separate libraries: each lists only its own EPDs.
Related
- Pagination: the shared cursor envelope.
- Authentication & Access: which endpoints need credentials.
- EPD Lifecycle: publishing is what puts an EPD into this list.