npm Package Health Score API — Dependency Risk Scorer
Pricing
from $3.30 / 1,000 results
npm Package Health Score API — Dependency Risk Scorer
npm package health score API. Rate any npm dependency 0-100 with a letter grade from keyless public signals: downloads, release recency & cadence, maintainers, license, deprecation, deps. One row per package with top risks. Keyless. A clean MCP tool for AI coding agents.
Pricing
from $3.30 / 1,000 results
Rating
0.0
(0)
Developer
Kyle Maloney
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
3 days ago
Last modified
Categories
Share
Score the health of any npm package from 0 to 100 with a letter grade, using only keyless, public signals — download volume and momentum, release recency and cadence, maintainer bus factor, license, version maturity, dependency surface, plus SLSA build provenance, registry signatures and install-script detection. Give it a list of package names and get back one clean, structured row per package so engineers and AI coding agents can decide whether a dependency is safe to adopt.
No API key. No login. No GitHub token required.
Who it's for
- Engineering leads vetting a dependency before it enters the tree.
- DevSecOps / AppSec teams triaging supply-chain risk across a lockfile.
- OSPO and procurement teams assessing license and maintenance posture.
- AI coding agents that need an "is this package safe to add?" tool.
What it does
- Runs a live drift check before anything is billed — a popularity canary, a document-shape check, and a negative control (a name that cannot exist must return 404). If npm's downloads API starts answering zero, the run fails rather than silently re-grading every package.
- Fetches the registry document and the 30-day download series, each with three attempts and backoff.
- Scores eight weighted inputs into a 0–100 composite and an A–F grade.
- Surfaces the supply-chain signals already inside the registry document: SLSA provenance, registry signature, install scripts, bundled types, engine range, repository, package age, total versions, and the deprecation reason.
How a failure is reported — read this before you trust a score
A score is only as good as its inputs, and a missing input changes the answer.
Measured live on 2026-08-01: express scores 87 (grade A) with real download
data and 59 (grade C) when api.npmjs.org does not answer — a two-grade swing,
with the row publishing downloads_last_month: 0 and the rationale
"Low adoption: 0 downloads/month" about a package doing 505,265,187
downloads a month.
So this actor distinguishes three states on every numeric input:
| Value | Meaning |
|---|---|
| A number | Fetched and real. |
0 | The source answered, and the answer is zero. |
null | Not fetched. Never read this as zero. |
downloads_statusisok,no_data(HTTP 404 — npm holds no download record, so0is a genuine answer), orunavailable(the API did not answer after three attempts, sodownloads_last_monthisnull).registry_statusisok,not_found(HTTP 404 — a verified negative: the package does not exist), orunavailable.health_scoreis published only when every weighted input was obtained. Otherwise it isnull,score_statusis"partial",score_inputs_missingnames exactly what is missing, andhealth_score_partialcarries the score renormalized over the weight that was evaluable.maintainers,dependencies_countanddeprecatedarenullwhen the registry document could not be read — not0,0andfalse. (Previously an unread version object scored full marks for "Zero runtime dependencies".)- If every package lookup fails, the run fails and emits nothing.
Example input
{"packages": ["express", "left-pad", "@babel/core"],"includeTrend": true}
Scoped packages are supported. You can also pass a single package string, or a
comma/space-separated list.
Output fields
| Field | Description |
|---|---|
package | The package name as requested. |
latest_version | Version on the latest dist-tag. |
health_score | 0–100 composite. null when any weighted input was unavailable. |
grade | A (≥85), B (≥70), C (≥55), D (≥40), F, or N/A. |
health_score_partial | The score renormalized over the inputs that were obtained. Not comparable with a complete health_score. |
score_status | complete, partial, or unavailable. |
score_inputs_missing | Which weighted inputs could not be obtained. |
score_weight_evaluated_pct | Share of the 100-point weight model actually evaluated. |
sub_scores | The eight 0–100 sub-scores. A sub-score is null when its input was missing. |
last_publish_date | Publish date of the latest version. |
days_since_publish | Days since that publish. |
publishes_last_2y | Releases in the last 730 days (the cadence input). |
publishes_last_1y | Releases in the last 365 days. |
downloads_last_month | 30-day download total. null when the API did not answer. |
download_trend_pct | Momentum: second half of the window vs the first. |
maintainers | Maintainer count (bus factor). null when the document was unreadable. |
license | Declared license string. |
deprecated | true / false / null. |
deprecated_message | The reason npm publishes, e.g. "use String.prototype.padStart()". |
dependencies_count | Runtime dependencies on the latest version. |
dev_dependencies_count | Declared devDependencies. |
top_risks | Ranked plain-language risks. Never asserts anything that was not measured. |
supply_chain_risks | Install scripts, missing provenance, missing signature, no repository, deprecation. Kept separate from top_risks. |
rationale | One-paragraph summary of positives and risks. |
package_age_days | Days since the first ever publish. |
first_publish_date | Date of the first ever publish. |
total_versions | Versions the registry holds. |
dist_tags | All dist-tags, sorted. |
has_prerelease_tag | True when tags other than latest exist (next, beta, latest-4, …). |
repository_url | Source repository, normalized to a browsable https URL. |
homepage | Declared package homepage. |
bugs_url | Declared issue tracker. |
description | Package description. |
keywords | Package keywords. |
unpacked_size_bytes | Installed size of the latest tarball. |
file_count | Files in the published tarball. |
tarball_url | Registry tarball URL. |
has_provenance | SLSA build-provenance attestation — the strongest published supply-chain trust signal. |
has_registry_signature | npm registry signature on the tarball. |
has_install_script | Arbitrary code runs on npm install. |
has_types | Ships bundled TypeScript definitions. |
engines_node | Declared supported Node range. |
has_funding | A funding target is declared. |
last_publisher | npm account that published the latest version. |
npm_stars | Users who have starred the package. |
registry_status | ok, not_found, or unavailable. |
registry_error | The transport error behind unavailable. |
downloads_status | ok, no_data, or unavailable. |
downloads_error | The transport error behind unavailable. |
row_partial | True when any source did not answer for this package. |
error | Present on a not-found or failed row. |
source_url | npmjs.com page for the package. |
retrieved_at | ISO timestamp of the lookup. |
Populating the optional columns. deprecated_message populates for a
deprecated package — the prefill (express, left-pad) exercises it, since
left-pad is deprecated with "use String.prototype.padStart()".
has_provenance is true for packages published with SLSA attestations
(@babel/core as of 2026-08-01) and false for express. engines_node,
has_funding and has_types vary by package; the three-package example above
covers all of them. registry_error, downloads_error and error are null
on a healthy run — that is good news, not a dead column.
Scoring model (weights sum to 100)
| Input | Weight | Notes |
|---|---|---|
| Download volume | 25 | log-scaled; 10M/month saturates. |
| Release recency | 20 | ≤30 days = 100, ≥730 days = 0. |
| Release cadence | 15 | ~8 releases in 2 years saturates. |
| Download trend | 10 | Second half of the 30-day window vs the first. |
| Maintainer count | 10 | Bus factor: 1 → 50, 2 → 75, 3+ → 100. |
| License | 10 | Permissive 100, copyleft 60, other 50, none 0. |
| Version maturity | 5 | ≥1.0.0 → 100. |
| Dependency count | 5 | 0 deps → 100, −5 per dep. |
A deprecated package is capped at 20 regardless of the other inputs. The weights are unchanged, so scores remain comparable with earlier runs.
Use as an MCP tool
Exposed to AI coding agents via mcp.apify.com as a "score this npm package"
tool. The three-state contract matters here: an agent must not read
downloads_last_month: null as zero adoption, or health_score: null as a bad
score. Check score_status first.
FAQ
Are scores comparable between runs? Yes — the weights have not changed. Only
compare health_score values carrying score_status: "complete".
Why is health_score sometimes null? Because a weighted input could not be
fetched. Use health_score_partial together with score_weight_evaluated_pct
for a labelled approximation.
Does it detect a typo'd package name? Yes — registry_status: "not_found"
with health_score: null, which is a verified negative rather than a low score.
A live negative control also fails the run if the registry ever starts returning
200 for names that do not exist.
Is this a determination? No. It is a screening tool over public registry signals. It does not audit code, run tests, or evaluate transitive risk.
Data sources (keyless, no anti-bot)
https://registry.npmjs.org/<package>— the full packument.https://api.npmjs.org/downloads/range/last-month/<package>— daily downloads.
Pricing (Pay Per Result)
Billed per dataset record returned: one row per package, including rows for packages that do not exist (a verified negative is an answer). Runs that fail a drift assertion, or lose every lookup, emit nothing and bill nothing.
Related actors
- SBOM Vulnerability Scanner — scan a whole lockfile against OSV.dev and the CISA KEV catalog, then score the flagged packages here.