The vendor ships no SDK for your language, and the registry offers three packages that claim to wrap the API. One has 40 stars and a friendly README. Nobody on the team can say who publishes it, whether it still tracks the API, or what runs when it installs, and the pull request adding it already has an approval.
What you get
You will end up with six checks that produce facts rather than impressions, and a verdict that separates what should block adoption from what you are choosing to live with. This is for you if you add third-party clients and want the decision written down.
Short answer
Check the license, whether the package runs a script on install, and whether anyone has published it in the last year. Those three are blockers. Then count the maintainers, the transitive dependencies, and the share of the API it covers, and record who publishes it. Those are costs to accept deliberately rather than reasons to walk away.
You will need
Node 22 or later, and the registry metadata and repository for each candidate. Everything the checks read is public: the registry metadata, the license field, and the repository’s release history.
Voxgig maintains the SDK Catalog. This page compares a catalog package with the provider’s own SDK, a community package, and a client you write.
Approaches compared
| Approach | When it fits | What it costs you | When to pick something else |
|---|---|---|---|
| A community package after an audit | Nothing official exists and the package covers what you call | An audit you repeat at every upgrade, and a maintainer who owes you nothing | The package fails any of the three blockers |
| A generated SDK Catalog package | You want full coverage of an API whose provider ships no client | An unofficial package, generated from a description, that tracks upstream on its own schedule | The provider publishes a client for your language |
| The provider’s official SDK | It exists for your language and covers the operations you call | Whatever the vendor decided about retries, errors and types, which you inherit | The official client is unmaintained or missing your language |
| Writing the client yourself | A handful of operations, or a vendor whose packages all fail the checks | Every operation and every upgrade is yours, forever | The surface is large enough that hand maintenance loses |
Run the checks before the discussion rather than after it. The argument in a pull request is usually about taste, and the facts settle most of it. A package with no license cannot be adopted, whatever anyone thinks of its API. A package published last week by six maintainers needs no defending.
The unofficial ones deserve one more question than the checks ask, which is what happens when the vendor changes the API. An official client ships with the change. A generated one ships when somebody regenerates. A community one ships when its maintainer has a free evening. That difference does not appear in any registry field, and it is the thing that bites.
Make the blockers absolute
The decision rests on three facts a script can check, so it needs no judgment call.
{
id: 'install-scripts',
level: 'blocker',
test: (p) => p.install_scripts === false,
say: () => 'runs a script on install, which executes code before you import anything',
},
An install script runs on every developer machine and every build agent, with the rights of whoever ran the install, before a single line of your code executes.1 A package that needs one may have a good reason, stated plainly in its README. What matters is that you decide, rather than finding out from an incident report.
License and recency are the same kind of fact. No license means no permission, whatever the README implies,2 and a package unpublished for two years against an API that shipped four versions is already behind.
Write that check as an allow, not a deny. A registry record usually omits the field rather than
setting it to none, so a test that rejects one sentinel string waves the absent case straight
through and reports a clean adopt. Require a license you recognize, and treat missing, empty and
UNLICENSED alike.
Turn the rest into costs you priced
Warnings are not weaker blockers. They are the bill.
{
id: 'provenance',
level: 'warning',
test: (p) => p.kind === 'official',
say: (p) => p.kind === 'generated'
? 'generated by a third party from the description, so an upstream change lands when they regenerate'
: 'published by somebody other than the API provider, with no commitment to track it',
},
Record the warnings in the pull request that adds the dependency. A year later the package has not been updated, the API has moved, and the question is what anyone knew at the time. That answer belongs in the history rather than in somebody’s memory.
Check it worked
Judge three candidates for the same API.
node demo.mjs
@meterco/sdk adopt
meterco-client reject
blocker license license is none
blocker install-scripts runs a script on install, which executes code before you import anything
blocker maintained last published 780 days ago, 0 releases in a year
warning bus-factor 1 maintainer, so an unavailable person is an unpatched package
warning dependency-weight 214 transitive dependencies come with it
warning provenance published by somebody other than the API provider, with no commitment to track it
warning coverage covers 12 of 61 operations
@voxgig-sdk/meterco adopt with a plan
warning provenance generated by a third party from the description, so an upstream change lands when they regenerate
The third verdict is the one to read carefully. Every mechanical check passes, but it is still not a plain adopt, because nobody at the API provider has promised anything about it. That is the same warning the community package carries, and the difference between them is the other six lines.
A plain adopt is also a real outcome, and the first row shows it. Six checks passing is not a formality: it means somebody publishes the package, keeps publishing it, and stands behind what it covers. Most of the packages you will run this against will not reach that line.
node --test judge.test.mjs
1..7
# tests 7
# suites 0
# pass 7
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 124.376254
When it goes wrong
A package passes and breaks a week later. The checks measure the package, not the API behind it. Add a contract test against the vendor’s sandbox, and run it on a schedule.
The coverage number is meaningless. Counting operations treats a rarely used endpoint the same as the one you call a million times. Count the operations you actually call instead, and keep that list beside the dependency so the next upgrade can be measured the same way.
Every candidate fails. The API is young or niche. Writing a thin client for the operations you need is a legitimate outcome of this exercise, and often the cheapest one.
The audit passes, but the supply chain is still open. Transitive dependencies change between installs. Commit a lockfile, and review what an upgrade pulls in rather than only what the top package did.
When not to do this
Do not run these checks and then ignore the blockers because a deadline is close. A package with no license is a legal problem that outlives the sprint, and adopting it now means removing it later, under worse conditions.
Do not treat a catalog package as an official one. The SDK Catalog generates clients for APIs whose providers were never asked, so nothing in it carries a support commitment from the vendor.
Do not re-run the checks only at adoption. Every one of them can change, and a package that was maintained when you adopted it is exactly the sort of thing nobody looks at again.
Do not let a high star count substitute for any of this. Stars measure attention at some point in the past, which is why a star count does not answer any of the six checks.3 A package can be popular, abandoned, and unlicensed at the same time.
Related how-tos
Last verified
Verified 2026-09-14 against Node 22.22.2. Both output blocks are what the preceding command printed.
Footnotes
-
Package managers disagree about this. npm’s scripts documentation lists
preinstall,install, andpostinstallamong whatnpm installruns. pnpm decided the other way. A dependency’s build script does not run unless the package is named inallowBuilds, and withstrictDepBuildsat its default of true, one nobody has reviewed fails the install outright. Although the two package managers share one registry, they give opposite answers to whether a stranger’s script should run on your machine by default. ↩︎ Back to text -
The default is older than any registry. choosealicense.com, which GitHub curates, states it in one paragraph. A creative work, code included, is under exclusive copyright by default. Without a license, nobody else may copy, distribute, or modify it without risking “take-downs, shake-downs, or litigation” from whoever wrote it. Once the work has other contributors, the page adds, “nobody” starts including you. A README that says feel free is a sentiment, and copyright does not read sentiments. ↩︎ Back to text
-
The star count has been audited by people other than GitHub. A measurement study posted in December 2024 put the suspected fake stars at 4.5 million in its title. It also found that a fake star promotes a repository for under two months, and counts against it after that. The revision of September 2025 retitled the paper “Six Million (Suspected) Fake Stars in GitHub” and changed “Scams” to “Spams” in the subtitle. The count grew by a third between drafts, which is the kind of growth a star count is meant to signal. ↩︎ Back to text