How health scoring works
A health score is a compression of many weak signals about a package into one number you can sort by. Understanding what goes into it — and what it deliberately leaves out — is the difference between using it well and being misled by it.
What a score actually represents
OSPulse computes a health score for every package it tracks. The score answers a narrow question:
Based on everything observable about this project right now, how likely is it to keep behaving like a dependable dependency over the next twelve months?
That is a forecast about a project, not a judgement about your use of it. A package can score poorly and still be entirely safe for you to depend on — a tiny, finished, single-purpose library that has not needed a commit in three years is "unmaintained" by every mechanical measure and perfectly fine in practice. Equally, a package can score well and still be wrong for your context: healthy, active, popular, and licensed in a way your legal team will not accept.
The score is directional evidence. It tells you where to look. It does not tell you what to do.
Scores are snapshots, not properties
Every score OSPulse produces is a dated snapshot with an identifier, captured against the evidence available at that moment. A package does not "have" a score; it has a history of scores. That distinction matters — see Dependency drift.
The signal categories
Scoring is built from independent signal categories. Each is observed and scored separately, then combined. Keeping them separate is what makes the number explainable: when a score moves, OSPulse can say which category moved it.
| Category | What it observes | Why it predicts risk |
|---|---|---|
| Maintenance & activity | Commit recency and rhythm, issue and pull-request response times, whether reported bugs get triaged at all | A project that has stopped responding will not respond to your incident either |
| Release cadence | Frequency and regularity of published releases, gap since the last one, whether releases still reach the registry | Predictable cadence is the clearest sign a maintainer is still present and shipping |
| Maintainer concentration | How many people actually merge and publish, and how much of the work sits with one account — the bus factor | Single-maintainer projects are one burnout, one lost account, or one bad day from an incident |
| Known vulnerabilities | Open advisories affecting the resolved version, their severity, and whether fixes exist upstream | The direct, conventional measure — necessary, and on its own insufficient |
| Licence posture | Declared licence, changes to it across versions, ambiguity or absence of a licence, restrictive terms | Licences change. A relicence between minor versions is a compliance event you will otherwise discover during due diligence |
| Deprecation | Registry deprecation flags, maintainer-declared end of life, successor packages named in metadata | An explicit "do not use this" from the author is the strongest possible signal, and the most frequently ignored |
| Adoption trend | Direction and shape of download and dependent-project counts over time | Absolute popularity is weak evidence; a collapse in adoption usually means the ecosystem moved on before you did |
| Compromise signals | Ownership and publisher changes, sudden maintainer additions, anomalous release patterns, install-script changes, name-similarity to popular packages | These are the fingerprints of supply-chain attacks — see Threat intelligence |
Two properties of this design are worth calling out.
Categories are not equally reliable. Activity signals are noisy — a quiet month means nothing; twelve quiet months mean a great deal. Deprecation signals are near-binary and highly reliable. The model treats them accordingly rather than averaging everything as though it carried equal information.
Absence of a signal is not a negative signal. If a project publishes no issue-tracker data because it does not use a public tracker, that is missing information, not bad information. Missing evidence lowers confidence rather than lowering the score — which is why confidence is reported as its own number.
Confidence is reported separately
Every health response carries overallHealthScore and confidenceScore as two independent values.
curl https://api.ospulse.app/api/v1/packages/$PACKAGE_ID/health \
-H "Authorization: Bearer $OSPULSE_TOKEN"{
"snapshotId": "8f3c1a52-6d0e-4a77-9b2e-2f9d0c1a7e44",
"capturedAtUtc": "2026-07-21T04:12:08Z",
"overallHealthScore": 74,
"confidenceScore": 88,
"riskLevel": "Moderate",
"evidenceBundleId": "b1d9e0f3-45a2-4c19-8f6b-0a3c7d2e5910"
}The full response also returns componentScores, the per-category breakdown behind the overall figure, and weightsSnapshotJson, the exact weighting configuration in force when the snapshot was taken. That second field exists so that a score computed six months ago remains reproducible even after you have retuned your weights.
Read the two numbers together:
overallHealthScoreintegerOptionalThe combined score. High is healthy. Treat it as a ranking key, not a threshold.
confidenceScoreintegerOptionalHow much evidence stands behind the score. A low confidence score means OSPulse could observe little about the project — common for private registries, mirrored packages, and projects that develop somewhere other than where they publish.
riskLevelstringOptionalA banded label derived from the score and its supporting evidence, for grouping and display. Use the numeric score when you need ordering; use the band when you need a category.
A low score with low confidence is not a finding
It is a request for a human to look. Escalating on unconfident scores is the fastest way to train your team to ignore the tool. Gate on evidence, not on uncertainty.
Evidence: the score shows its working
A score you cannot interrogate is a score nobody will act on. Every snapshot references an evidence bundle — the underlying observations that produced it, including activity and maintainer snapshots captured at the time.
curl https://api.ospulse.app/api/v1/packages/$PACKAGE_ID/evidence \
-H "Authorization: Bearer $OSPULSE_TOKEN"Alongside it, two endpoints give you the same picture at different altitudes:
GET /api/v1/packages/{packageId}/risk-indicators— the discrete concerns OSPulse has flagged for this package, each one individually reviewable. This is what you want in a triage queue.GET /api/v1/packages/{packageId}/summary— a condensed narrative of the package's current state, suitable for a ticket or a review comment.
The rule of thumb: sort by score, decide on evidence. If you find yourself acting on a number without ever opening the indicators behind it, the number has become a superstition.
History: the score is only interesting as a series
A single score tells you where a package is. A series tells you where it is going, which is almost always the more useful fact.
curl "https://api.ospulse.app/api/v1/packages/$PACKAGE_ID/health/history?take=24" \
-H "Authorization: Bearer $OSPULSE_TOKEN"Each entry is a dated snapshot, so you can plot the trajectory and see the shape of a decline rather than its endpoint. Three shapes recur:
- Step changes — a single event moved the score. A new advisory, a deprecation flag, a maintainer handover. These are the ones to alert on.
- Slow slides — cadence stretching, issues going unanswered, releases thinning out. Nothing happened on any given day, which is exactly why nobody noticed.
- Recoveries — a new maintainer takes over, releases resume. Worth catching too; teams carry migration plans for packages that recovered a year ago.
For a step change, POST /api/v1/ai/score-change-root-cause will explain which category moved and what observation triggered it, so you are not reverse-engineering the delta by hand.
Tuning the weights for your organisation
Default weights encode a general-purpose opinion about risk. Your organisation has a specific one. Scoring weights are therefore a per-tenant setting.
curl https://api.ospulse.app/api/v1/tenants/current/scoring-weights \
-H "Authorization: Bearer $OSPULSE_TOKEN"curl -X PATCH https://api.ospulse.app/api/v1/tenants/current/scoring-weights \
-H "Authorization: Bearer $OSPULSE_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "...": "weight overrides" }'Changing weights changes what your team argues about, so change them deliberately:
- A regulated product with a long support obligation should weight maintainer concentration and deprecation heavily. You are underwriting these dependencies for years; the question is who will still be there.
- A fast-moving internal service can tolerate more maintenance risk and should weight known vulnerabilities and compromise signals more sharply. You can replace a library next quarter; you cannot un-ship a compromised build.
- Organisations with strict licence obligations should raise licence posture until a licence change is loud enough to stop a merge. See Policies for turning that into an enforced gate.
Retune rarely, and record why
Weights are effectively the definition of "healthy" for your organisation. Because each snapshot stores the weights it was scored with, historical scores stay meaningful after a change — but your dashboards will shift the day you apply one. Treat a weight change like a schema migration, not a preference toggle.
Benchmark profile
A score in isolation invites the wrong question ("is 74 good?"). The useful question is comparative: good relative to what. The benchmark profile describes your organisation — sector, size, and the shape of your estate — so comparisons are drawn against a peer group rather than against every OSPulse tenant.
curl https://api.ospulse.app/api/v1/tenants/current/benchmark-profile \
-H "Authorization: Bearer $OSPULSE_TOKEN"Update it with PATCH on the same path. Benchmark output is available through GET /api/v1/analytics/benchmark, and participation is opt-in via PUT /api/v1/analytics/benchmark/opt-in.
How to read a score well
A short list of habits that separate teams who get value from scoring from teams who quietly stop looking at it.
Rank, don't threshold. "Everything below 60 is banned" produces a queue of exceptions and a team that resents the tool. "Review the ten worst-scoring packages in our critical services this quarter" produces work that gets done.
Compare against the package's own past. A package that dropped fifteen points this month deserves more attention than one that has sat at a mediocre score for three years without incident. Movement is signal; level is context.
Weight by your own exposure. A poor score on a build-time development dependency used by one internal tool is not the same finding as the same score on a library that ships in your product and parses untrusted input. GET /api/v1/packages/{packageId}/usage tells you where the package actually appears in your estate; combine the two before deciding anything.
Never let the score be the only input. Read the risk indicators. Look at whether the maintainer said anything. A human glance at the repository is worth more than any aggregate — the score's job is to tell you which repositories deserve that glance.
What health scoring is not
It is not a vulnerability scanner. Known advisories are one category among many, and a package with zero CVEs can still be the riskiest thing in your build.
It is not a malware verdict. Compromise signals raise suspicion and surface anomalies; concluding that a specific release is malicious is a threat-intelligence determination, not a scoring one.
It is not a quality measure. Health scoring says nothing about whether the code is well written, well tested, or well suited to your problem. It measures whether the project around the code is alive and behaving normally.
It is not stable across time by design. If your score never moves, either nothing is changing upstream — unlikely — or you are not looking often enough.
