Skip to content

EU compliance checks

EU compliance checks

qr3.app ships with a built-in EU validator for Digital Product Passports. It checks your DPPs against the relevant EU frameworks (ESPR, Loi AGEC) and returns structured issues with severity, field path, hints, and an optional ESPR-ready badge.

There are three entry points:

  1. Server-side, for persisted DPPsGET /v1/dpp/:id/eu-compliance
  2. Live preview in the create formPOST /v1/dpp/validate
  3. Live simulator on the detail pagePOST /v1/dpp/:id/validate-update

All three return the same EuComplianceResult shape — so you only need to implement the consumer once.

Result shape

{
"compliant": false,
"espr_ready": true,
"issues": [
{
"code": "TEXTILE_AGEC_REQUIRED",
"severity": "error",
"field": "textile_data.country_weaving_knitting",
"message": "AGEC chain country is required for products sold on the French market",
"hints": { "market": "FR" }
},
{
"code": "TEXTILE_GREENWASHING",
"severity": "warning",
"field": "product_name",
"message": "\"natural\" is a prohibited term under AGEC",
"hints": { "term": "natural", "locale": "en", "category": "generic" }
}
],
"summary": { "errors": 1, "warnings": 1, "info": 0 }
}
  • compliant: truesummary.errors === 0
  • espr_ready: true ⇔ all ESPR opt-in fields set (see Textile DPP)
  • issues[] is stably sorted by severity (errorwarninginfo), then by code

Severity levels

SeverityMeaningExample
errorBlocks EU compliance; should be fixed before releaseTEXTILE_AGEC_REQUIRED when FR is in market and origin chain is incomplete
warningRecommendation; customer may save, but should actTEXTILE_GREENWASHING outside FR
infoStatus signal, no action requiredTEXTILE_ESPR_READY — all ESPR opt-in fields complete

Rule catalog (textile + battery)

Textile (Q3.3.4)

CodeSeverityTrigger
TEXTILE_AGEC_REQUIREDerror (FR) · warning (else)AGEC chain (weave/dye/assembly) incomplete, FR in market
TEXTILE_MICROPLASTICS_CONSISTENCYerrorSynthetics ≥ 50% but contains_microplastics !== true
TEXTILE_SVHC_THRESHOLDwarningSVHC entry without CAS number or concentration
TEXTILE_GREENWASHINGerror (FR) · warning (else)Prohibited term in product_name/material
TEXTILE_ESPR_READYinfoAll ESPR fields B7–B15 complete

Battery (Phase 4a)

The validator checks limits from the EU battery regulation 2023/1542 (recycled content thresholds, carbon footprint classes, minimum warranty, …). Details in the battery DPP docs.

Live preview in the create form

When you create a DPP in the dashboard (/dashboard/dpp/new), the same validator runs stateless on every validate roundtrip — no need to save the passport. The eu_compliance section appears directly under the existing validation panel and highlights errors and warnings before submit.

Direct API usage:

Terminal window
curl -X POST https://qr3.app/v1/dpp/validate \
-H "Authorization: Bearer $API_KEY" \
-H "X-Workspace-Id: $WS_ID" \
-H "Content-Type: application/json" \
-d '{
"category": "textile",
"gtin": "04012345678901",
"product_name": "Natural Cotton T-Shirt",
"manufacturer": "EcoWear GmbH",
"origin_country": "PT",
"market_countries": ["FR"],
"textile_data": { "fiber_composition": [{ "material": "cotton", "percentage": 100, "recycled_pct": 0 }] }
}'

The response contains:

  • data.valid — syntactic validity (Zod)
  • data.errors[] — classic Zod errors
  • data.jsonld_preview — JSON-LD preview
  • data.eu_compliance — EU validator result (null when the Zod validation fails)

When eu_compliance.summary.errors > 0, the dashboard renders a save-guard banner right above the submit button — the save is not blocked, but you see the consequences before you click.

Live simulator on the detail page

On /dashboard/dpp/:dppId, below the compliance section, there is a simulator card. It lets you change the two most compliance-relevant fields (status and market_countries) and see the impact immediately — without touching persisted data. Typical flow:

  1. Preview: “What happens if I add FR to my markets?” → pick chip “FR” → click “Preview EU impact” → TEXTILE_AGEC_REQUIRED becomes visible
  2. Fix: complete the origin chain (outside the simulator, via create or bulk flow)
  3. Save: “Save changes” persists status/market list via PUT /v1/dpp/:id

Direct API usage:

Terminal window
curl -X POST https://qr3.app/v1/dpp/$DPP_ID/validate-update \
-H "Authorization: Bearer $API_KEY" \
-H "X-Workspace-Id: $WS_ID" \
-H "Content-Type: application/json" \
-d '{ "market_countries": ["DE", "FR"], "status": "live" }'

The response extends the standard result with a preview object:

{
"data": {
"compliant": false,
"espr_ready": false,
"issues": [/* … */],
"summary": { "errors": 1, "warnings": 0, "info": 0 },
"preview": {
"status": "live",
"landing_locale": "en",
"market_countries": ["DE", "FR"],
"changed_fields": ["status", "market_countries"]
}
}
}

changed_fields[] lists only fields that would actually change — so your UI can highlight exactly those chips.

An empty body is allowed and returns the current compliance verdict (re-check the stored state).

Automation

CI pipeline

A typical CI check:

Terminal window
STATUS=$(curl -s -o /tmp/out.json -w "%{http_code}" \
https://qr3.app/v1/dpp/$DPP_ID/eu-compliance \
-H "Authorization: Bearer $API_KEY" \
-H "X-Workspace-Id: $WS_ID")
if [ "$STATUS" != "200" ]; then
exit 1
fi
jq -e '.data.summary.errors == 0' /tmp/out.json > /dev/null

MCP server

The bundled @qr3/mcp-server exposes a validate_dpp tool, among others — AI agents can use it to check, before any create call, whether the given data meets AGEC rules.

Common issues

IssueCauseFix
TEXTILE_MICROPLASTICS_CONSISTENCYPolyester share ≥ 50%, flag still falseSet contains_microplastics: true
TEXTILE_AGEC_REQUIRED with hints.market: "FR"FR in market, chain incompleteSet country_weaving_knitting, country_dyeing_printing, country_assembly
TEXTILE_GREENWASHING as errorProhibited term + FR in marketRemove term from product_name or adjust market
TEXTILE_SVHC_THRESHOLDSVHC substance without CASAdd the CAS number from the REACH list

Next steps