ClearlyDefined is a free, open source service run under the Open Source Initiative (OSI) that serves license, copyright and source location data for open source components through a public REST API. It harvests packages with open scanning tools, computes a "definition" for each exact version, and lets anyone propose corrections as reviewed pull requests, so every consumer benefits from one fix.
If your SBOM has blank or NOASSERTION license fields, ClearlyDefined is usually the first place to look before a human reads LICENSE files by hand. This post explains who runs it, how its coordinates map to purl, what a definition contains and how its scores are computed, how curation works, and how to query it with curl to fill license gaps in an SPDX or CycloneDX SBOM. It ends with a hands-on walkthrough for an npm package and a Maven artifact, and the mistakes teams make when they treat the data as more than it is.
Key takeaways
- ClearlyDefined's charter states its mission as a global database of licensing metadata "for every software component ever published". The data is published under CC0 and the code under MIT, according to the project FAQ.
- It is an OSI project, donated to the OSI in 2018. Under a memorandum of understanding signed in early 2026, the OSI delegated day to day operations to AboutCode, the nonprofit behind ScanCode, and a three year roadmap followed in April 2026.
- Every component is addressed by five part coordinates,
type/provider/namespace/name/revision, which map closely to purl. A missing namespace is written as a hyphen. - A definition separates the declared license (what the project says) from discovered licenses (what scanners found in files), adds attribution parties, source location, release date, file level detail and two scores.
- Corrections are curations: YAML files in a public GitHub repository, proposed through the website or a
PATCH /curationscall, reviewed in the open. - The data is factual metadata, provided as is. A high score means the metadata is complete and consistent, not that the license is safe for your use. Legal decisions stay with your counsel and your policy.
What is ClearlyDefined and what problem does it solve?
Open source license compliance has a data problem before it has a legal problem. To meet the obligations of an MIT, Apache 2.0 or LGPL component you need three facts for the exact version you ship: the license or licenses that apply, the parties you must credit, and where the corresponding source lives. Package registries carry a declared license field, but it is free text in some ecosystems, missing in many packages, and silent about the files inside the package that carry different terms. Every company consuming the same package ends up rediscovering the same facts and fixing the same gaps in private.
ClearlyDefined's charter describes exactly this duplication: as SBOMs become expected for compliance and security, organizations "will have to fix the same missing or wrongly identified licensing metadata over and over again." The project's answer is a shared cache. It harvests the data once, publishes the raw tool output and a summarized definition, and accepts corrections from anyone through an open review process, so a fix contributed by one consumer reaches every other consumer.
The charter scopes the project to licensing metadata: the declared and observed license, copyright holders, and source location including the revision or commit. It lists five principles, paraphrased here: neutral, with no company driven focus; open data, infrastructure and processes; factual, with no interpretation or assessment; upstream first, helping origin projects fix their own metadata; and simple solutions wherever possible.
That "factual" principle matters more than any other for how you use the data. ClearlyDefined tells you what a project declared and what tools found in its files. It does not tell you whether a license is compatible with your product, and the charter is explicit that data consumers receive it "as-is with no guarantees or warranties."
The four operations: harvest, curate, contribute, serve
The charter breaks the work into four operations:
- Harvest. Pull data from upstream projects, from reading metadata in canonical locations up to full source analysis with open tools. Raw output is stored in its native form and made available. New tools are admitted by community vote.
- Curate. Curators, the data equivalent of committers, review harvested data and community contributions in public pull requests. The charter says that, at least initially, curated data must be signed off by two curators.
- Contribute. Push clarified data back to the origin project in whatever form that project prefers, with care not to spam maintainers with automated pull requests. A project that adopts the fix effectively curates itself.
- Serve. Make the harvested, summarized and curated data available through a REST API and a browsable website.
Who runs ClearlyDefined?
The project FAQ states that ClearlyDefined "is currently managed by the Open Source Initiative", that it started in a corporate open source programs office and quickly attracted foundations and companies, and that its code is MIT licensed and its data CC0. The OSI's February 2026 announcement says the project was donated to the OSI in 2018, and that foundations including the Linux Foundation and the Eclipse Foundation make extensive use of its data for popular projects they host.
Governance is set out in the charter. A Governing Board has three voting members: the Executive Director of the OSI, the Steering Committee Chair and the Outreach Committee Chair. The board sets strategic direction and manages budget, infrastructure and policies. The Steering Committee owns technical direction and oversight of the harvest, curate, contribute and serve processes. The Outreach Committee runs promotion, events, education and communications. Formal votes, for example to appoint curators, run for at least a working week and pass with two positive votes and no substantiated negative vote.
Two recent events shape where the project is going. In early February 2026 the OSI announced, in a post datelined Brussels, that it and AboutCode, the nonprofit behind the ScanCode toolkit, had signed a memorandum of understanding on stewarding and sustaining ClearlyDefined, after the OSI Board endorsed the model following community review. The later roadmap post explains that the agreement, formalized at FOSDEM, delegates day to day operations of ClearlyDefined to AboutCode while the OSI remains the steward.
The 2026 to 2028 roadmap
On 14 April 2026 the OSI published a three year roadmap proposal developed by AboutCode with the community. Its stated goals include combining ClearlyDefined and AboutCode operations and backends where possible so scans are reused rather than repeated, refactoring the backend and frontend to cut technical debt and improve data quality, enrolling sponsors to share cloud compute costs, unlocking data access, and enabling security and project health use cases. It is organized in three phases:
| Year | Focus in the proposal |
|---|---|
| 2026 | Reduce technical debt and operating costs; improve the harvesting and curation queue and the user interface; make community contributions easier |
| 2027 | Move to a federated data architecture; start work on vulnerability, project health and risk use cases |
| 2028 | Deploy security related use cases; deliver project health insights and analytics |
Individual items include prioritizing scans by actual package usage, improving ScanCode's speed and its license detection using ClearlyDefined's curated data, mirroring data across hosts for availability, and moving the definition store to an open source document database. It is a proposal, so treat dates as intentions rather than commitments.
Dated timeline
| Date | Event | Source |
|---|---|---|
| 2018 | Project donated to the OSI | OSI blog, February 2026 |
| 21 Sep 2023 | Snapshot of the ClearlyDefined dataset used for a license popularity study | OSI blog, 7 Dec 2023 |
| 7 Dec 2023 | OSI publishes the most popular licenses per language, built on that snapshot | OSI blog |
| 16 May 2024 | New documentation site and community push announced | ClearlyDefined blog and OSI blog |
| 9 Jul 2024 | Support for non SPDX licenses through ScanCode's LicenseRef mapping | ClearlyDefined blog |
| 6 Aug 2024 | GUAC, an OpenSSF project, adopts ClearlyDefined license metadata | ClearlyDefined blog |
| 13 Nov 2024 | ClearlyDefined v2.0 adds over 2,000 newly identifiable well known licenses (LicenseRefs) | ClearlyDefined blog |
| Feb 2026 | OSI and AboutCode sign a memorandum of understanding; day to day operations move to AboutCode | OSI blog and ClearlyDefined blog |
| 14 Apr 2026 | Three year roadmap for sustainability and growth published | ClearlyDefined blog |
How are components identified? Coordinates and purl
Everything in ClearlyDefined is keyed by coordinates, documented as a five part path:
type/provider/namespace/name/revision
- type is the shape of the component:
npm,maven,pypi,git,sourcearchiveand so on. The documentation compares it to a MIME type. - provider is where the component lives:
npmjs,mavencentral,github,pypi,nuget. - namespace scopes the name: the npm scope, the Maven groupId, the GitHub org. If there is no namespace you must still fill the slot with a hyphen.
- name is the simple name: the npm package name, the Maven artifactId, the repository name.
- revision is the exact instance: a package version, or a full commit hash for git components. The service treats it as an opaque string.
So lodash 4.17.21 is npm/npmjs/-/lodash/4.17.21, a scoped npm package is npm/npmjs/@fluentui/react-text/9.0.0-alpha.13, and Apache HttpCore 4.4.16 is maven/mavencentral/org.apache.httpcomponents/httpcore/4.4.16.
Mapping coordinates to purl
The coordinates documentation includes a purl mapping. As a rule, purl type maps to coordinate type, purl namespace to namespace, purl name to name and purl version to revision, with the provider supplied from the ecosystem. The exceptions are where most lookup bugs come from:
| Ecosystem | purl example | ClearlyDefined coordinates | Note |
|---|---|---|---|
| npm | pkg:npm/lodash@4.17.21 | npm/npmjs/-/lodash/4.17.21 | Scope goes in namespace |
| Maven Central | pkg:maven/org.apache.httpcomponents/httpcore@4.4.16 | maven/mavencentral/org.apache.httpcomponents/httpcore/4.4.16 | Three Maven providers: mavencentral, mavengoogle, gradleplugin |
| Maven source | sources of httpcore 4.3 | sourcearchive/mavencentral/org.apache.httpcomponents/httpcore/4.3 | Separate definition linked to the package |
| PyPI | pkg:pypi/backports.ssl-match-hostname@3.7.0.1 | pypi/pypi/-/backports.ssl_match_hostname/3.7.0.1 | Check name normalization: the docs use the name as published on PyPI |
| Cargo | pkg:cargo/bitflags@1.0.4 | crate/cratesio/-/bitflags/1.0.4 | purl cargo becomes type crate |
| CocoaPods | pkg:cocoapods/SoftButton@0.1.0 | pod/cocoapods/-/SoftButton/0.1.0 | purl cocoapods becomes type pod |
| GitHub | pkg:github/ratatui-org/ratatui@bcf4368... | git/github/ratatui-org/ratatui/bcf4368... | Revision is a full commit hash |
| Go | pkg:golang/rsc.io/quote@v1.3.0 | go/golang/rsc.io/quote/v1.3.0 | Slashes inside the namespace are URL encoded as %2f |
| Debian | pkg:deb/debian/mini-httpd@1.30-0.2?arch=arm64 | deb/debian/-/mini-httpd/1.30-0.2_arm64 | Architecture joins the revision; arch=source becomes type debsrc |
| Conda | pkg:conda/absl-py@0.4.1?build=...&channel=main&subdir=linux-64 | conda/anaconda-main/linux-64/absl-py/0.4.1-py36h06a4308_0 | Channel becomes provider; subdir becomes namespace |
Two of these deserve extra care. Go module paths contain slashes, so golang.org/x/crypto must be encoded as go/golang/golang.org%2fx/crypto/<version>; the using-data page also documents an alternative API form for namespaces with many slashes. And Maven and Debian each create two definitions per release, one for the binary package and one for the source (sourcearchive or debsrc), which the curation guidelines say should be reviewed together.
What data does a definition hold?
A definition is the summarized, curated view of one component revision. The documentation groups it into domains. "ClearlyDescribed" means the essential descriptive facts are present; "ClearlyLicensed" means the key license facts are present. A "Secure" domain is described as still being fleshed out, so do not expect vulnerability data from ClearlyDefined.
Here is the shape of the live definition for lodash 4.17.21, trimmed, as returned by the API on 25 October 2026:
{
"described": {
"releaseDate": "2021-02-20",
"sourceLocation": {
"type": "git", "provider": "github",
"namespace": "lodash", "name": "lodash",
"revision": "f299b52f39486275a9e6483b60a410e06520c538",
"url": "https://github.com/lodash/lodash/tree/f299b52f..."
},
"projectWebsite": "https://lodash.com/",
"issueTracker": "https://github.com/lodash/lodash/issues",
"hashes": { "sha1": "6795...", "sha256": "6a08..." },
"files": 1054,
"tools": ["clearlydefined/1.3.4", "reuse/6.4.0", "licensee/9.18.1", "scancode/32.7.0"],
"score": { "total": 100, "date": 30, "source": 70 }
},
"licensed": {
"declared": "CC0-1.0 AND MIT",
"facets": {
"core": {
"files": 1054,
"discovered": { "unknown": 1049, "expressions": ["MIT", "MIT AND CC0-1.0"] },
"attribution": { "unknown": 1050, "parties": ["Copyright OpenJS Foundation and other contributors https://openjsf.org", "..."] }
}
},
"score": { "total": 75, "declared": 30, "discovered": 0, "consistency": 15, "spdx": 15, "texts": 15 }
},
"coordinates": { "type": "npm", "provider": "npmjs", "name": "lodash", "revision": "4.17.21" },
"_meta": { "schemaVersion": "1.7.0", "updated": "2026-10-14T08:19:42.532Z" },
"scores": { "effective": 87, "tool": 87 }
}
The described domain
- sourceLocation is the exact source that produced the package, as its own set of coordinates plus a URL and an optional path. For lodash it is a specific commit on GitHub. This is the field you need for "written offer" and source availability obligations, and for any team that wants to rebuild or patch a dependency from source.
- releaseDate is the ISO 8601 date the component was released, generally to the day.
- projectWebsite and issueTracker may change over time, and the docs say they may be updated at any time.
- hashes let you tie the definition to the exact artifact you downloaded. If your lockfile integrity hash does not match, you are looking at a different artifact.
- tools records which harvest tools and versions fed the definition.
Facets
Many projects ship only a small part of their repository. The documentation gives TypeScript as an example: the package holds about 200 files while the full source tree has more than 36,000, mostly tests. ClearlyDefined models this with facets, glob based groupings of files: core, data, dev, docs, examples and tests. core is whatever no other facet claims. Licensing is reported per facet, so if you consume only the core of a component you only need the core facet's data.
The licensed domain
Each facet carries:
- declared: the SPDX license expression declared for the component as a whole, for example the
licenseproperty in package.json or the LICENSE file at the root of a repository. - files: the number of files in the facet.
- discovered.expressions: the unique SPDX expressions found across all files.
- discovered.unknown: how many files had no discernible license information.
- attribution.parties: the unique entities to credit, usually copyright holders.
- attribution.unknown: how many files had no discernible attribution.
The lodash example shows why the split matters. The npm registry metadata for lodash 4.17.21 declares MIT, yet ClearlyDefined's declared value is CC0-1.0 AND MIT, because the package's LICENSE file also states that rights to sample code are waived via CC0. A tool that reads only package.json would record MIT and miss the second license entirely. Meanwhile 1,049 of 1,054 files carry no license header, which is normal for JavaScript and why the declared license does most of the work.
File level data
Behind the summary, the definition has a files array with a path, hashes, detected license and attributions for each file. That is the level a curator uses to resolve a NOASSERTION file, and the level you need when a single vendored file carries a different license from the package around it.
How is a definition computed from harvested tool output?
The architecture documentation describes a set of stateless services that talk over REST and shared storage:
| Part | Role |
|---|---|
| Website | React app that talks almost only to the service |
| Service | Node service with REST APIs for definitions, curations, harvesting, search and notices |
| Crawler | Horizontally scalable Node service that runs harvest tools on queued components |
| Harvest queue | Requests waiting for a crawler |
| Harvest store | Raw tool output, stored as blobs |
| Curation store | A structured GitHub repository, clearlydefined/curated-data |
| Definition store | Computed definitions, cached |
| Tools | Open analysis tools such as ScanCode, Licensee, REUSE and FOSSology, plus small in house utilities |
The pipeline for one component looks like this:
- Queue. A request to harvest coordinates lands in the harvest queue, either because someone asked for it, because a definition was requested that does not exist yet, or because the project schedules it.
- Fetch. A crawler downloads the artifact from the provider and, where it can, locates the matching source.
- Run tools. Each admitted tool runs and its output is stored in full under a tool name and version. The live lodash record shows four: the project's own extractor, REUSE, Licensee and ScanCode.
- Summarize. The service converts each tool's native output into the definition shape: Licensee mostly informs the declared license from the root LICENSE file, ScanCode produces per file license and copyright detections, REUSE reads machine readable license annotations, and the ClearlyDefined extractor reads registry metadata such as release date and source location.
- Aggregate. Summaries are merged into one definition, with precedence rules deciding which tool wins for each field.
- Curate. Any merged curation for those coordinates is applied on top.
- Score and cache. Scores are computed and the definition is cached until new harvest output or a new curation invalidates it.
The architecture page says the service does little compute itself; the heaviest work is summarizing, aggregating and curating definitions, which is done once and cached. That explains a practical quirk: the _meta.updated timestamp on a definition reflects the last recompute, not the release date.
How the scores work
The live definitions expose scores for each domain and an overall scores.effective value. From the lodash and HttpCore records returned on 25 October 2026:
| Domain | Component | Points seen | What earns them |
|---|---|---|---|
| Described | date | 30 | A release date is known |
| Described | source | 70 | A source location is known |
| Licensed | declared | 30 | A declared license is present |
| Licensed | discovered | 0 of 25 | Share of core facet files that have both a license and an attribution, times 25 |
| Licensed | consistency | 15 | Every discovered expression is satisfied by the declared license |
| Licensed | spdx | 15 | The declared value parses as a strict SPDX expression |
| Licensed | texts | 15 | Every referenced license has its text present in the files |
These weights come from the open source service code (business/definitionService.ts in the clearlydefined/service repository), which sets declared 30, discovered 25, consistency 15, spdx 15 and texts 15 for the licensed domain, and date 30 and source 70 for the described domain. The effective score is the average of the described total and the licensed total, rounded down: here 100 and 75 give 87. The toolScore values are the same calculation run before curations are applied, so the gap between scores.tool and scores.effective tells you how much curation changed the picture.
The discovered component explains why so many JavaScript packages lose 25 points. lodash has 1,054 files and only a handful carry both a license header and a copyright line, so the ratio rounds to zero even though the package is cleanly licensed.
Read the scores as a completeness signal. A 100 means the metadata is complete and consistent. It says nothing about whether the license fits your distribution model, and a low score on a small JavaScript package often only means its files carry no headers, which the declared license covers in practice.
How do curations work?
A curation is a correction layered over harvested data. Curations live as YAML files in the public clearlydefined/curated-data repository on GitHub, one file per component path, with entries per revision. Anyone can propose one; curators merge them.
There are two ways to contribute:
- Website. On clearlydefined.io, search for the component, open its definition, click the pencil icon, edit the fields, and press contribute. The site opens a pull request against the curated data repository and you follow the discussion on GitHub.
- API. Send
PATCH https://api.clearlydefined.io/curationswith a JSON body holdingcontributionInfoandpatches.contributionInfocarries asummary(the pull request title),details, aresolutionthat points to your evidence, and atypefrom a fixed list: Missing, Incorrect, Incomplete, Ambiguous or Other. Each patch names coordinates without a revision and maps one or more revisions to the fields being changed, for examplelicensed.declared. File level curation is also supported.
The license values curators use
The curation guidelines allow five values for a license property:
| Value | Meaning |
|---|---|
| A valid SPDX expression | The license or licenses identified, joined with AND, OR or WITH |
NOASSERTION | License like text was found but could not be matched to an SPDX identified license |
NONE | A human confirmed there is no license information in the file |
OTHER | A human confirmed there is a license, but it has no SPDX identifier |
| No value | Automated tools found nothing license like |
Curators follow the SPDX Matching Guidelines; text that fails them is curated as OTHER. Note the asymmetry: NONE and OTHER are human assertions, NOASSERTION and an empty value are tool outcomes. Your policy engine should treat them differently.
Source versus package components
The guidelines separate a source component (the preferred form for editing, such as a git repository) from a package component (the form a package manager installs). The declared license of a source component comes from canonical locations for its ecosystem; discovered licenses come from reviewing every file marked NOASSERTION or carrying an AND or OR expression. For package components, the guidelines note that most obligations flow from source to package, so package definitions should take the source into account. For Maven and Debian, the package and source definitions are created together and should be curated together.
Review and governance
The charter set a two curator sign off rule at the outset. It also describes curators as "more librarian and data scientist than lawyer", nominated on merit, acting as individuals rather than for an employer, and not held responsible for errors in merged data. Contributors are expected to back changes with evidence: a link to the LICENSE file at the exact tag, the POM's licenses block, the project's own statement.
How good is coverage?
Coverage depends on the ecosystem and on demand. The providers documented today include npm, Maven Central, Google's Maven repository, the Gradle plugin portal, PyPI, NuGet, RubyGems, crates.io, Packagist, CocoaPods, Conda channels, Go, Debian and GitHub repositories. Popular versions of popular packages are usually harvested and often curated. Older, obscure or very new versions may not be harvested until someone asks, and requesting a missing definition queues a harvest rather than returning data immediately.
The OSI's December 2023 study of a 21 September 2023 snapshot of the dataset found MIT and Apache 2.0 far ahead of other licenses, with preferences varying by ecosystem, and a substantial share of components with no license or a NOASSERTION value. That gap is exactly what the project is designed to make visible. Treat any lookup as one of three outcomes: a definition with a declared license, a definition with gaps, or no definition yet. Plan your SBOM pipeline for all three.
How do you query the ClearlyDefined API?
The production API lives at https://api.clearlydefined.io, with interactive Swagger documentation at /api-docs/. A development instance runs at dev-api.clearlydefined.io. The endpoints most teams need:
| Endpoint | Method | Use |
|---|---|---|
/definitions/{type}/{provider}/{namespace}/{name}/{revision} | GET | One definition |
/definitions | POST | Many definitions in one call, body is a JSON array of coordinate strings |
/definitions?pattern=... | GET | Search for coordinates |
/notices | POST | Generate an attribution notice for a list of coordinates |
/harvest | POST | Queue harvesting for coordinates |
/curations/{type}/{provider}/{namespace}/{name}/{revision} | GET | Curations that apply |
/curations | PATCH | Propose a curation |
/badges/{type}/{provider}/{namespace}/{name}/{revision} | GET | Score badge image for a README |
Three details from the service source are worth knowing. Adding ?expand=-files to a definitions request drops the per file array, which shrinks responses dramatically when you only need summaries. A definition can be previewed with a pending curation applied through /definitions/{coordinates}/pr/{number}, which is how reviewers check a pull request's effect. And a harvest request body is a JSON array of objects where only coordinates is required, with at most 1,000 entries per request.
The notices endpoint takes a body with a coordinates array, an optional output of text, html, template or json, and template options. Its response has the rendered content and a summary that lists warnings for coordinates with no definition, no license or no copyright, which is a useful checklist in itself.
Rate limits
The using-data page documents limits on production: POST to /definitions, /curations and /notices is limited to 250 requests per minute each, and all other endpoints to 2,000 requests per minute. The development host allows 500 requests per five minutes for other endpoints. The page names x-ratelimit-limit and x-ratelimit-remaining headers; a batch call we made on 25 October 2026 returned ratelimit-limit: 250 and ratelimit-remaining without the prefix, so read both forms. Back off on HTTP 429. For bulk SBOM enrichment, the batch POST is the polite option: one request for many coordinates.
How does ClearlyDefined relate to SPDX and CycloneDX?
ClearlyDefined does not define a format. The FAQ calls SPDX, OpenChain and similar efforts complementary: they bring tools, processes and formats, and ClearlyDefined brings the data.
SPDX License List and expressions
The declared field and every discovered.expressions entry is an SPDX license expression built from identifiers on the SPDX License List (version 3.29.0, dated 16 September 2026, at the time of writing), combined with AND, OR and WITH for exceptions. That makes the data drop into an SPDX document or a CycloneDX expression field without translation, and it means the parsing rules in our post on SPDX license expressions in SCA reports apply unchanged.
LicenseRefs for licenses outside the SPDX list
Some well known licenses have no SPDX identifier. Since July 2024, ClearlyDefined can report them using ScanCode's mapping to LicenseRef-scancode-... identifiers, and the v2.0 release in November 2024 added more than 2,000 newly identifiable licenses this way. SPDX allows LicenseRef- identifiers for exactly this purpose. If your policy engine rejects anything not on the SPDX list, decide how to treat LicenseRefs before you start consuming the data, or you will see a sudden rise in "unknown license" failures that are actually precise identifications.
Where ClearlyDefined fields land in an SBOM
| ClearlyDefined field | SPDX 2.3 package field | CycloneDX 1.6 component field |
|---|---|---|
licensed.declared | PackageLicenseDeclared | licenses[] with expression, or license.id, and acknowledgement: declared |
licensed.facets.core.discovered.expressions | PackageLicenseInfoFromFiles | No direct equivalent; record as evidence, and only your reviewed result as acknowledgement: concluded |
| your organization's decision | PackageLicenseConcluded | licenses[] with acknowledgement: concluded |
attribution.parties | PackageCopyrightText | copyright |
described.sourceLocation | PackageDownloadLocation or an external reference | externalReferences of type vcs |
described.hashes | PackageChecksum | hashes |
described.releaseDate | ReleaseDate | not a core field; use a property |
| coordinates | external reference of type purl | purl |
The CycloneDX 1.6 schema defines the acknowledgement values declared and concluded, and describes declared licenses as the authors' initial intention and concluded licenses as the result of analysing the codebase. SPDX 2.3 draws the same line between PackageLicenseDeclared and PackageLicenseConcluded. The concluded license is the decision your organization makes after weighing declared, discovered and context. ClearlyDefined helps you make it; it should not silently become it.
Declared, discovered or ClearlyDefined: which do you trust?
| Signal | Where it comes from | Typical accuracy | Blind spots | When to trust it |
|---|---|---|---|---|
| Package manifest declared license | license in package.json, <licenses> in a POM, classifiers or License-Expression in Python metadata | Good for well maintained packages; free text and blanks are common in older ones | Says nothing about vendored files, bundled assets, or license text that adds terms | As a first pass, and when it is a valid SPDX expression that matches the LICENSE file |
| Scanner discovered licenses | Your own file level scan of the artifact you actually ship | High recall for headers and license texts; precision depends on the tool and on human review | Noise from tests and docs, matches on license mentions that are not grants, slow at scale | When you scanned the exact artifact and reviewed NOASSERTION hits |
| ClearlyDefined definition | Harvested tools plus community curations for the exact coordinates | Good for popular packages; curations fix many manifest errors | Coverage gaps for obscure or brand new versions; facets may include files you do not ship; data is as is | When coordinates and hashes match your artifact and the score is high, as an input to a concluded license your policy owns |
The pragmatic order is: match coordinates and hashes, take ClearlyDefined's declared license if present and consistent with the manifest, look at discovered expressions for additional licenses in the core facet, and send anything with NOASSERTION, OTHER or a disagreement to human review.
Step by step: look up an npm and a Maven component, compare, and propose a curation
You need curl and jq. Nothing here writes to production until step 7, and step 7 is optional.
Step 1: build the coordinates from a purl
# npm, no scope -> namespace is "-"
NPM_COORD="npm/npmjs/-/lodash/4.17.21"
# Maven Central: groupId -> namespace, artifactId -> name
MVN_COORD="maven/mavencentral/org.apache.httpcomponents/httpcore/4.4.16"
Step 2: fetch both definitions
API="https://api.clearlydefined.io"
curl -s "$API/definitions/$NPM_COORD" -o lodash.json
curl -s "$API/definitions/$MVN_COORD" -o httpcore.json
If a definition does not exist yet, the response will have no licensed data and few tools. That is your cue to request a harvest (step 6) rather than to assume the package is unlicensed.
Step 3: print the license facts side by side
for f in lodash.json httpcore.json; do
jq -r --arg f "$f" '
"\($f)",
" declared: \(.licensed.declared // "none")",
" discovered: \(.licensed.facets.core.discovered.expressions // [] | join(" | "))",
" unknown files: \(.licensed.facets.core.discovered.unknown // "n/a") of \(.licensed.facets.core.files // "n/a")",
" parties: \(.licensed.facets.core.attribution.parties // [] | length)",
" source: \(.described.sourceLocation.url // "none")",
" score: \(.scores.effective) (tool \(.scores.tool))",
" tools: \(.described.tools | join(", "))"
' "$f"
done
On 25 October 2026 that printed, for lodash, a declared CC0-1.0 AND MIT, discovered MIT | MIT AND CC0-1.0, 1,049 of 1,054 files without a license header, three attribution parties and an effective score of 87. For HttpCore 4.4.16 it printed a declared Apache-2.0, discovered Apache-2.0, 256 of 260 files without detections, one party (Copyright 2005-2022 The Apache Software Foundation) and an effective score of 87.
Step 4: compare with what the registry says
# npm registry metadata for the same version
npm view lodash@4.17.21 license
# The POM's licenses block for the same Maven version
curl -s https://repo1.maven.org/maven2/org/apache/httpcomponents/httpcore/4.4.16/httpcore-4.4.16.pom \
| sed -n '/<licenses>/,/<\/licenses>/p'
This is a real example of the inheritance trap. On 25 October 2026 npm view returned MIT for lodash, and the httpcore-4.4.16.pom had no <licenses> block at all. The declaration lives in the parent POM, httpcomponents-core-4.4.16.pom, which names the Apache License, Version 2.0. Maven's POM reference documents that child POMs inherit elements such as licenses from their parent, so a tool that reads only the artifact's own POM records nothing, while ClearlyDefined, which also analyses the files inside the jar and its linked source, reports Apache-2.0.
curl -s https://repo1.maven.org/maven2/org/apache/httpcomponents/httpcomponents-core/4.4.16/httpcomponents-core-4.4.16.pom \
| sed -n '/<licenses>/,/<\/licenses>/p'
Compare the three answers:
- Manifest says MIT, ClearlyDefined declared says
CC0-1.0 AND MIT: ClearlyDefined read the LICENSE text and found an additional grant. Your concluded license should account for both. - Manifest and ClearlyDefined agree, discovered agrees: high confidence. Record it.
- They disagree with no obvious reason: open the LICENSE file at the exact tag from
sourceLocationand decide.
Step 5: verify you are looking at the same artifact
# sha1 of the tarball npm actually serves
curl -sL https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz | shasum -a 1
jq -r '.described.hashes.sha1' lodash.json
The two values should match. If your build uses a private mirror, a repackaged artifact or a patched fork, the ClearlyDefined definition describes upstream, not your artifact.
Step 6: fill gaps in an SBOM in one batch call
Take the components in a CycloneDX SBOM that have no license, map their purls to coordinates, and ask for all of them at once.
# Components with a purl and no licenses in a CycloneDX JSON SBOM
jq -r '.components[] | select((.licenses // []) | length == 0) | .purl' bom.json > missing-purls.txt
# Convert simple npm and maven purls to coordinates (extend for other ecosystems)
python3 - <<'EOF' > coords.json
import json, urllib.parse
out = []
for line in open("missing-purls.txt"):
p = line.strip()
if not p.startswith("pkg:"):
continue
body = p[4:].split("?")[0]
typ, rest = body.split("/", 1)
path, _, ver = rest.rpartition("@")
parts = [urllib.parse.unquote(x) for x in path.split("/")]
name = parts[-1]
ns = "/".join(parts[:-1]) or "-"
provider = {"npm": "npmjs", "maven": "mavencentral", "pypi": "pypi", "nuget": "nuget", "gem": "rubygems"}.get(typ)
if provider:
out.append(f"{typ}/{provider}/{ns}/{name}/{ver}")
print(json.dumps(out))
EOF
# One POST for the whole list (production limit: 250 POSTs per minute)
curl -s -X POST "$API/definitions" \
-H "Content-Type: application/json" \
--data @coords.json -o definitions.json
# Show what came back
jq -r 'to_entries[] | "\(.key)\t\(.value.licensed.declared // "MISSING")\t\(.value.scores.effective // 0)"' definitions.json
Write the results back as declared licenses, not concluded ones, and keep the coordinates and _meta.updated timestamp alongside so an auditor can see where the value came from. For any coordinate marked MISSING, queue a harvest:
curl -s -X POST "$API/harvest" \
-H "Content-Type: application/json" \
-d '[{"coordinates":"npm/npmjs/-/some-package/1.2.3"}]'
The harvest schema requires only coordinates, accepts optional tool and policy fields, and the route rejects more than 1,000 entries per request. Harvesting takes time, so poll the definition later rather than queueing the same coordinates in a tight loop; the service also throttles repeated harvests.
Step 7: propose a curation, with evidence
Suppose you find that a Maven artifact's definition has no declared license, while the parent POM at the exact version declares Apache 2.0. The safest route for a first contribution is the website: open the definition, click the pencil, set the declared license, and cite the URL of the parent POM. The site opens the pull request for you.
The API route uses the same information:
cat > curation.json <<'EOF'
{
"contributionInfo": {
"summary": "com.example:widget 1.2.3 declared license",
"details": "Declared license inherited from the parent POM at the same version.",
"resolution": "https://repo1.maven.org/maven2/com/example/widget-parent/1.2.3/widget-parent-1.2.3.pom",
"type": "Missing"
},
"patches": [
{
"coordinates": { "type": "maven", "provider": "mavencentral", "namespace": "com.example", "name": "widget" },
"revisions": { "1.2.3": { "licensed": { "declared": "Apache-2.0" } } }
}
]
}
EOF
# Validate the JSON locally first
jq . curation.json > /dev/null && echo "valid JSON"
# Then, only for a real correction you can evidence:
# curl -X PATCH https://api.clearlydefined.io/curations \
# -H "accept: application/json" -H "Content-Type: application/json" \
# --data @curation.json
The example coordinates are placeholders; do not submit them. A real curation creates a public pull request that volunteers review, so send only corrections you can prove, one component per pull request, with a resolution link that points at the exact version.
Common mistakes
- Treating a score as legal advice. The score measures completeness and consistency of metadata. A score of 100 on a GPL 3.0 component is still a GPL 3.0 component. Your policy decides what is allowed.
- Ignoring the revision. Licenses change between versions. A definition for 4.17.20 says nothing reliable about 4.17.21, and leaving the revision off returns the latest version where the provider supports it, which is rarely what your lockfile pins.
- Trusting declared licenses without discovered ones. A package that declares MIT can bundle a font, a minified third party file or a vendored directory under other terms. Read
discovered.expressionsfor the facets you actually ship. - Getting the coordinates wrong. Forgetting the hyphen for a missing namespace, using the purl type
cargoinstead ofcrate, or not URL encoding slashes in Go module paths all return empty or wrong definitions that look like "no license". - Copying discovered expressions into the concluded field. Discovered values include test fixtures and documentation unless facets exclude them. Concluded licenses are a human or policy decision.
- Assuming the artifact matches. Private mirrors, rebuilt packages and patched forks change the bytes. Compare hashes before applying the data.
- Treating NOASSERTION as no license.
NOASSERTIONmeans license like text exists that was not identified. It needs review, not a default. - Hammering the API. Single GET requests for thousands of components hit the rate limits. Use the batch POST and cache results keyed by coordinates and
_meta.updated. - Submitting curations without evidence. Unsupported curations waste curator time and are likely to be rejected. Link the exact file at the exact tag.
FAQ
What is ClearlyDefined used for?
It is used to look up license, copyright and source location data for a specific version of an open source package, mostly to fill gaps in SBOMs, generate attribution notices and check what a package declares against what its files contain.
Is ClearlyDefined free to use?
Yes. The API and website are free, the data is published under CC0 and the code is MIT licensed, according to the project FAQ. Production rate limits apply: 250 POST requests per minute on the bulk endpoints and 2,000 requests per minute on others.
Who maintains ClearlyDefined?
The Open Source Initiative stewards it under a charter with a Governing Board, a Steering Committee and an Outreach Committee. Under a memorandum of understanding signed in early 2026, day to day operations are delegated to AboutCode, the nonprofit behind ScanCode.
What is the difference between declared and discovered licenses?
The declared license is what the project says covers it overall, from a manifest field or a root LICENSE file. Discovered licenses are what scanners found in individual files. A component can declare one license and contain files under others.
How do I convert a purl to ClearlyDefined coordinates?
Map purl type to type, namespace to namespace, name to name and version to revision, add the provider for the ecosystem, and use a hyphen when there is no namespace. Watch the exceptions: cargo becomes crate, cocoapods becomes pod, GitHub becomes git/github, and Go namespaces need %2f for slashes.
What does the ClearlyDefined score mean?
It is a 0 to 100 measure of how complete and consistent the metadata is, averaged across the described and licensed domains. It does not rate the license or the package's safety.
Can I correct a wrong license in ClearlyDefined?
Yes. Use the pencil icon on the definition page or the curations API to propose a curation with a link to evidence. It becomes a public pull request in the curated data repository, reviewed by curators.
Does ClearlyDefined include vulnerability data?
Not today. Its security domain is described in the documentation as still being fleshed out, and the 2026 roadmap proposal only starts vulnerability and project health work in 2027. Use vulnerability databases and your SCA tooling for that.
Can I put ClearlyDefined data straight into my SBOM?
You can put it in as declared license and copyright data with its provenance recorded. Keep the concluded license as a separate decision made by your policy or your legal team.
Why does ClearlyDefined say a package has no data?
Usually because that exact version has not been harvested yet, or because the coordinates are wrong. Check the coordinates, then queue a harvest and try again later.
Where Safeguard fits, honestly
Safeguard is not a license database and does not replace ClearlyDefined's community curations. What Safeguard does is generate and ingest SBOMs for repositories, container images and SBOM files, parse SPDX license expressions, classify licenses as permissive, weak copyleft, strong copyleft or network copyleft, evaluate license policies as code with gates in CI, and generate NOTICE and attribution files. ClearlyDefined is the kind of shared, upstream data source that makes those policy decisions better informed, and the declared versus concluded discipline in this post is how we recommend teams record it. When a license problem turns into a dependency change, autonomous remediation covers the license category as well as vulnerabilities, and it ships switched off so your team chooses what runs on its own.
Software supply chain hygiene still matters here because license metadata and vulnerability exposure come from the same inventory: the SBOM you enrich with ClearlyDefined is the same list of components and versions that will need patching when a CVE lands. Getting coordinates, versions and source locations right once pays off on both sides.
Fix it without waiting for upstream
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
See how Safeguard would help
The first demo is a real Safeguard CLI session: the assistant reads a finding and its advisory, asks before any command that changes something, edits package.json to a fixed lodash version and runs npm install and npm test with your approval, then shows /scan .. The same session is where you would scan a repository, look at the components and versions it carries, and then check their coordinates against ClearlyDefined before you record licenses.
Click through it at your own pace, or open it full screen.
The second demo sets up the local runner on your own machine, approves it with a device code and scans from the terminal. Running scans locally keeps source and SBOMs on your hardware, which suits teams that want to enrich license data offline from cached ClearlyDefined results.
Click through it at your own pace, or open it full screen.
The third demo connects Claude to the Safeguard MCP server with OAuth and chooses which tools Claude can use. With license tools enabled, you can ask about a component's license findings in plain language and then cross check the answer against its ClearlyDefined definition.
Click through it at your own pace, or open it full screen.
To try the CLI yourself, download Safeguard and follow the CLI documentation.
Quick check
What are the ClearlyDefined coordinates for the npm package lodash version 4.17.21?
npm/npmjs/-/lodash/4.17.21. The type is npm, the provider is npmjs, the hyphen fills the empty namespace slot, and the revision is the exact version.
A definition has an effective score of 100. Does that mean the license is safe to use?
No. The score measures whether the metadata is complete and consistent. Whether a license fits your product is a policy and legal decision.
What is the difference between NOASSERTION and NONE in a curated license field?
NOASSERTION means license like text was found but not matched to an SPDX identifier. NONE means a human confirmed the file contains no license information.
Sources
- ClearlyDefined website
- ClearlyDefined documentation home
- ClearlyDefined charter
- ClearlyDefined FAQ
- ClearlyDefined coordinates and purl mapping
- Being ClearlyDefined: described, facets and licensed domains
- ClearlyDefined architecture
- Using the ClearlyDefined data, rate limits and coordinates
- ClearlyDefined curation guidelines
- Curation via UI
- Curation via API
- ClearlyDefined API documentation (Swagger)
- Live definition: npm lodash 4.17.21
- Live definition: Maven httpcore 4.4.16
- clearlydefined/curated-data on GitHub
- clearlydefined/service on GitHub
- clearlydefined/crawler on GitHub
- OSI: Unveiling ClearlyDefined (May 2024)
- ClearlyDefined blog: Beyond SPDX, expanding licenses identified by ClearlyDefined
- ClearlyDefined blog: OSI and AboutCode sign MoU
- ClearlyDefined blog: three year roadmap
- OSI: ClearlyDefined v2.0 adds support for LicenseRefs
- OSI: GUAC adopts license metadata from ClearlyDefined
- OSI: The most popular licenses for each language in 2023
- OSI and AboutCode sign MoU on ClearlyDefined
- ClearlyDefined three year roadmap
- ClearlyDefined service source: definitionService.ts (scoring weights)
- ClearlyDefined service source: notices route
- ClearlyDefined service source: harvest request schema
- SPDX License List
- SPDX 2.3 specification: package information
- SPDX 2.3 specification: license expressions
- SPDX License Matching Guidelines and templates
- CycloneDX 1.6 JSON schema
- ECMA-427, Package-URL (purl) specification
- npm documentation: package.json license field
- Maven POM reference: inheritance and licenses
- Maven Central: httpcomponents-core 4.4.16 parent POM
- REUSE specification 3.3
- ScanCode Toolkit documentation
Last reviewed: 25 October 2026.