Runs the service criteria of dpp-criteria against listed Digital Product Passport (DPP) services and reports "N of M automated checks passed" per service and run.
Results are results of automated checks only. They are no certification and do not establish a presumption of conformity. dpp-validator is part of a research project and shares what we currently know; the list of services makes no claim to be complete and is not a legal assessment.
Passport criteria (a published passport behind a product identifier) are checked by dpplint; this repository covers the behaviour of the DPP service and its API.
- Services of other operators. Since 18 October 2026 the list also includes DPP services of other operators that we found in our own research (entries from our research in dpp-criteria/services). They are listed and checked without the consent of their operators, only through endpoints that anyone can reach on the public internet: read-only requests at most once a week, or our own short-lived test instance of open software. Their example passports are checked with dpplint once a week. Such entries are marked as our own research wherever results are shown and may be incomplete or out of date.
- What the results are not. The results are results of automated checks against criteria we maintain. They are no quality seal, no certification and no presumption of conformity. They do not mean that anyone endorses them: listing a service does not mean that its operator agrees with the criteria or the results, and no authority, standards body or funding body has approved the criteria or this list.
- Our own role. OwnYourData maintains the criteria (dpp-criteria), runs dpp-validator and dpplint, and operates one of the listed services, the OwnYourData DPP Service. That service goes through the same criteria and the same code as every other entry. It is shown first on the result pages because they are linked from its start page; the order is no ranking. Changes to the criteria and to this repository are made in public pull requests.
- Correction or removal. An operator can ask at any time for its entry to
be corrected or removed, by e-mail to
office@ownyourdata.eu or with an issue or a
pull request in dpp-criteria. We answer within five working days.
- Correction: we check the request and correct the entry, or explain why we keep it; a disputed result of a criterion is discussed in an issue in dpp-criteria. An operator can also take over the entry with a pull request (see services/README.md).
- Removal: within five working days at the latest, the entry is deleted
from dpp-criteria and the service is no longer checked; its page and
latest results disappear from the result pages with the next published
run. Results of earlier runs remain on the branch
results. - The same applies to entries we maintain with the consent of their operator and to the entry of our own service.
These repositories and services of OwnYourData belong together. Each is a project of its own; the list shows how they depend on each other.
- dpp-criteria – the criteria
and the list of services. dpp-validator reads it at the commit pinned in
DPP_CRITERIA_REF. - dpplint – checks the passport behind a product identifier, at https://dpplint.ownyourdata.eu. dpp-validator calls its API for the example passports of every entry.
- DIDlint – validates DID documents, at https://didlint.ownyourdata.eu; dpplint calls it for the DIDs in a passport.
- SOyA (source) – the passport criteria of dpp-criteria are SOyA structures with SHACL shapes; dpplint validates with the SOyA web-cli.
- OYDID
(source) – the DID method
did:oyd; DIDlint resolvesdid:oydthrough the OYDID resolver.
- Check types
tlsandhttpfor all criteria withtarget: serviceandmethod: automated. A step withcompare_with_resolvedis not evaluated in this version; its criterion isskippedwithreason_code: not_evaluated. automated-authandself-declaredcriteria are listed asskippedwith the reason and areason_code; passport criteria are listed undernot_run.- The self-declarations of an entry (
declarations) are listed with the statement and the link to the evidence as the operator gives them, in the JSON result and in a section of their own on the result page, apart from the checks. They are not checked and never count as passed. - Features a service lists under
not_implementedgiveskippedwithreason_code: not_implemented; features it does not declare at all givenot_applicable. - Two kinds of entries in
services/of dpp-criteria (entry.source): entries maintained by the operator are run in full every day; entries from our research are run read-only (only GET, HEAD and OPTIONS) once a week, or not at all ifcheck.modeisnone.--read-onlymakes any run read-only; criteria with other methods areskippedwithreason_code: not_sent. - Open software without a public instance (
check.mode: test-instance-weekly) is run in full once a week on a short-lived test instance of our own, started with the current release and discarded after the run (test-instances/). The results name the software version; certificate, TLS and HTTP version checks and the criteria the entry lists undertest_instance.not_applicableareskippedwithreason_code: not_applicable, because they would describe our test environment. - The example passports of an entry (
example_passports) are checked with the API of dpplint in the same run as the service: daily for operator entries, weekly for research entries, also when the service itself is not checked. - Run with GitHub Actions (
.github/workflows/daily.yml, every day at about 04:17 UTC, the weekly checks on Mondays; started by hand with the inputweeklyfor both): the JSON results are kept on the branchresults(runs/<date>/,latest/, dpplint results underlinter/), and the result pages andavailable-services.jsonare published on GitHub Pages: https://ownyourdata.github.io/dpp-validator/. - Requests carry the User-Agent
dpp-validator/<version> (+https://github.com/OwnYourData/dpp-validator; office@ownyourdata.eu)and go one after the other. - Passport criteria with
check.history(DPP-ID-002, the product identifier stays resolvable) are rated by dpp-validator itself, from the results of the criterion they name (DPP-ID-001) in the stored runs of the last days on the branchresults; dpplint skips them. The rating replaces dpplint's entry in the dpplint result of the passport (see "Linter result").
The criteria are not copied into this repository. They are read at run time
from a dpp-criteria checkout at a fixed commit; the Docker image contains the
commit pinned in DPP_CRITERIA_REF, and every result names
the commit it was produced with. Criteria and service entries are checked
against the JSON Schemas of the same checkout.
CRITERIA-FORMAT.md
defines the check types; the comments at the top of each file under
lib/dpp_validator/checks/ say how this runner applies them. Gaps in the
format are resolved by pull requests to dpp-criteria, not in the runner.
Per criterion the result is passed, warning (passed, with a remark),
failed or skipped (with a reason and a reason_code: not_applicable,
not_implemented, no_evidence, needs_credentials, not_sent,
unreachable or not_evaluated, see "Results" in CRITERIA-FORMAT.md). Only
criteria with status: active count in "N of M"; passed and warning count
as passed, skipped is not counted. Criteria with status: proposed are
reported separately as "proposed, not counted".
./build.sh
docker run --rm oydeu/dpp-validator:latest test
docker run --rm -v "$PWD/results:/app/results" oydeu/dpp-validator:latest run --service ownyourdata-dpp-servicerun checks one entry: in full for an operator entry, read-only for an entry
from our research, in full on a running test instance with
--test-instance <info file> (see test-instances/);
an entry with check.mode: none is refused. cycle does what the scheduled
workflow does for one cadence, with the dpplint checks of the example
passports; --test-instances <dir> gives the info files of the started test
instances:
docker run --rm -v "$PWD/out:/app/out" oydeu/dpp-validator:latest cycle --cadence daily --output out
docker run --rm -v "$PWD/out:/app/out" oydeu/dpp-validator:latest cycle --cadence weekly --output out--history <dir> names the directory runs/ of the stored results (a
checkout of the branch results); the workflow passes it, so that DPP-ID-002
is rated from it. Without it, DPP-ID-002 stays as dpplint reports it
(skipped).
services --cadence daily|weekly lists the entries whose service is run with
that cadence, services --test-instances those checked on our own test
instance.
A service that is not listed is checked from a local entry file, read-only:
docker run --rm -v "$PWD/local:/app/local" oydeu/dpp-validator:latest run --service local/service.yaml --output local/result.json --read-onlydocker run --rm -v "$PWD/out:/app/out" -v "$PWD/site:/app/site" oydeu/dpp-validator:latest site --results out --output site --this-service ownyourdata-dpp-service
writes the result pages from the entries of the dpp-criteria checkout and the
JSON results, as the workflow does: site/index.html (overview),
site/services/<id>.html (one page per entry), site/results/,
site/linter/ and site/available-services.json.
./build.sh <ref> builds with another dpp-criteria commit, branch or tag
(resolved to its commit). The JSON result is written to
results/<service id>.json; --output - prints it instead.
Without Docker (Ruby 3.1 or later, a dpp-criteria checkout next to this one):
bundle install
DPP_CRITERIA_DIR=../dpp-criteria bundle exec rake test
bin/dpp-validator run --criteria ../dpp-criteria --service ownyourdata-dpp-serviceThe checks need a direct connection to the service: TLS and HTTP version checks are meaningless behind a proxy that terminates TLS.
{
"validator": { "name": "dpp-validator", "version": "0.1.0" },
"notice": "Results of automated checks only. ...",
"service": { "id": "...", "name": "...", "operator": "...", "api_base": "...", "features": [], "not_implemented": [] },
"run_at": "2026-09-28T12:00:00Z",
"mode": "full",
"dpp_criteria": { "repository": "https://github.com/OwnYourData/dpp-criteria", "commit": "<full commit>" },
"summary": {
"text": "N of M automated checks passed", "passed": 0, "failed": 0, "warnings": 0, "skipped": 0,
"skipped_by_reason": { "not_applicable": 0 },
"proposed_not_counted": { "text": "...", "passed": 0, "failed": 0, "warnings": 0, "skipped": 0, "skipped_by_reason": {} }
},
"criteria": [
{ "id": "DPP-API-013", "version": 1, "status": "proposed", "title": "...",
"description_url": "https://github.com/OwnYourData/dpp-criteria/blob/<commit>/criteria/README.md#dpp-api-013", "level": "MUST",
"target": "service", "method": "automated", "check_type": "http", "result": "passed",
"messages": [], "details": ["step 1 (GET /dpps/{dppId}): HTTP 200 over HTTP/2"], "counted": false }
],
"declarations": {
"notice": "Statements of the operator about the service, with a link to its evidence, listed as given. ...",
"items": [
{ "criterion": "DPP-ACC-015", "title": "...", "status": "proposed",
"description_url": "https://github.com/OwnYourData/dpp-criteria/blob/<commit>/criteria/README.md#dpp-acc-015",
"statement": "...", "evidence": "https://..." }
]
},
"not_run": [ { "id": "DPP-DAT-014", "target": "passport", "reason": "..." } ]
}messages holds failures and warnings (severity error or warning),
reason and reason_code the reason for skipped, details what the runner
observed. mode is full, read-only or test-instance; a run on a test
instance has test_instance (software, version, release, images,
started_at, setup, test_passport, test_data) and no api_base in
service. skipped_by_reason counts the
skipped criteria per reason_code (only codes that occur).
description_url links to the readable description of the criterion in
criteria/README.md of the dpp-criteria commit used; it is absent if that
commit has no such page. declarations is present only if the entry has
self-declarations; title, status and description_url of an item come
from its criterion and are absent if the commit used has no such criterion.
cycle writes linter/<id>.json for every entry with example passports:
{
"service_id": "ownyourdata-dpp-service",
"checked_at": "2026-10-18T04:20:00Z",
"dpplint": "https://dpplint.ownyourdata.eu",
"passports": [
{ "product_id": "https://...", "label": "reference passport", "lint_url": "https://dpplint.ownyourdata.eu/?productId=...",
"status": "checked", "dpp_criteria": "<commit>",
"summary": { "text": "5 of 5 automated checks passed", "passed": 5, "failed": 0, "total": 5, "warnings": 1, "skipped": 1, "proposed_failed": 0 },
"criteria": [ { "id": "DPP-ID-016", "title": "...", "status": "active", "result": "warning" } ] }
]
}status is checked or error (with error, e.g. when dpplint cannot be
reached).
A criterion with check.history (DPP-ID-002) is rated by dpp-validator from
the stored runs (--history), and its entry carries the rating instead of
dpplint's skipped:
{ "id": "DPP-ID-002", "title": "...", "status": "proposed", "result": "passed",
"message": "DPP-ID-001 passed in all 9 runs from 2026-10-18 to 2026-10-26",
"history": { "criterion": "DPP-ID-001", "period_days": 30, "runs": 9, "failed": 0,
"from": "2026-10-18", "to": "2026-10-26" } }The result of history.criterion is taken from the stored runs of the last
period_days days (runs/<date>/linter/<id>.json, one per day; runs of
today are replaced by this run) and from this run. A run in which it has no
result is left out. failed after fail_after_consecutive_runs consecutive
failed runs within the period, warning after single failed runs, passed
if it passed in every run; with fewer runs than fail_after_consecutive_runs
the criterion is skipped (not_evaluated). The summary of the passport is
then counted again, as dpplint counts. The result page shows such a
criterion with its message also while it is proposed.
The list of all entries with their latest results, for pages that show it
(e.g. the start page of a DPP service). Entries come in this order: the entry
named with --this-service, then operator entries, then entries from our
research, each by name.
{
"generated_at": "2026-10-18T04:25:00Z",
"notice": "Results of automated checks only, from a research project ...",
"counting": "passed: active criteria with result passed or warning; ...",
"services": [
{
"id": "ownyourdata-dpp-service", "name": "OwnYourData DPP Service", "url": "https://dpp-service.ownyourdata.eu/",
"provider": { "name": "OwnYourData", "country": "AT" },
"this_service": true,
"entry": { "source": "operator", "as_of": "2026-10-18" },
"licence": { "text": "Apache-2.0", "spdx": ["Apache-2.0"] },
"linter": { "passports": [
{ "product_id": "https://...", "label": "reference passport", "lint_url": "https://dpplint.ownyourdata.eu/?productId=...",
"summary_text": "5 of 5 automated checks passed", "passed": 5, "total": 5, "failed": 0, "warnings": 1,
"checked_at": "2026-10-18", "note": null } ] },
"validator": { "status": "checked", "mode": "full", "cadence": "daily",
"details_url": "https://ownyourdata.github.io/dpp-validator/services/ownyourdata-dpp-service.html",
"summary_text": "15 of 15 automated checks passed", "passed": 15, "total": 15, "failed": 0,
"warnings": 0, "proposed_failed": 1, "self_declarations": 5, "checked_at": "2026-10-18" }
}
]
}Counting, for linter and validator alike: passed are the active
criteria with result passed or warning, failed those with result
failed, total is passed + failed, and warnings says how many of the
passed ones have a warning (they are included in passed). Skipped criteria
are not counted. summary_text is the text of dpplint or dpp-validator; a page
shows these numbers as "5 of 5 passed · 1 warning".
linter.passportsis empty, withlinter.note"no public example passport", if the entry lists none. A passport not checked yet hasnote: "first check pending", one whose check failed the error innote; both without numbers.validator.statusischecked,pending(no run yet) or, for an entry withcheck.mode: none, itscheck.reason:needs_credentials,no_public_instanceorno_en18222_paths.modeisfull,read-onlyortest-instance(in full on our own short-lived test instance; thentest_instancegivessoftware,versionandreleaseof the last run),cadencedailyorweekly.self_declarationsis the number of self-declarations in the latest result; they are not checked and not counted.
lib/dpp_validator/checks/– one class per check type, registered inchecks.rbtest-instances/– setups of our own short-lived test instances, one directory per entrylib/dpp_validator/transport/– HTTP/1.x and HTTP/2 over TLS with ALPN, TLS version probeslib/dpp_validator/from_dpplint/–EcmaRegexp,IRegexp,HeaderAssertion, copied unchanged from dpplint (source commit in each file header), with their tests undertest/from_dpplint/test/– Minitest; local HTTPS test servers with their own CA, HTTP/1.1 and HTTP/2assets/oyd-mark.svg– the OwnYourData mark next to the title of the result pages, also their iconconfig/funding.yml– the funding notice in the footer of the result pages and in this README, the one place where its wording is kept
http-2 (MIT), janeway-jsonpath
(MIT; match()/search() and comparisons with a bare @ are adapted in
json_path.rb), json_schemer (MIT).
The DPP Service Validator is part of PACE-DPP – Promoting and Accelerating a Digital Product Passport based Data-Service Ecosystem. This Lighthouse Project has been made possible by financial contributions from the Austrian Federal Ministry of Innovation, Mobility and Infrastructure (BMIMI, formerly BMK), supported by the Austrian Research Promotion Agency (FFG) under grant number 917177, as well as from the German Federal Ministry of Research, Technology and Space (BMFTR, formerly BMWK), supported by the German Research Promotion Agency (DLR-PT).
Apache License 2.0 – see LICENSE.