# pkgproof > A package-verification oracle for npm. Returns one verdict backed by evidence reasons, each labelled fact or heuristic with its source, and fails loud rather than vouch for a package it could not check. Scope is npm only. PyPI and crates.io are roadmap, not shipped. ## The contract - POST https://x402.pkgproof.net/v1/verify with `{"ecosystem": "npm", "name": "", "version": ""}` - $0.05 per verification: x402 `exact` on eip155:8453, USD Coin (0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) - A completed verification returns 200. `block` and `does_not_exist` are answers, not errors. ## How to call it 1. POST with no `X-PAYMENT` header: curl -sS -X POST https://x402.pkgproof.net/v1/verify \ -H 'Content-Type: application/json' \ -d '{"ecosystem": "npm", "name": "crossenv"}' 2. The 402 carries the terms to sign in `accepts[0]`, in the body and, base64, in the `PAYMENT-REQUIRED` response header. The header is x402 v2's canonical location; the body carries the same object and is the easier read. `amount` is the price in the asset's own units. 3. Sign them, resend the identical request with the authorisation in `X-PAYMENT`. The 200 carries the verification. ## Try it without paying The first call from an address each UTC day is served free, verdict and all, so you can see what you would be buying before funding a wallet. No key, no signup, nothing to send: just call it. After that the same address gets a 402 with the terms, and `free_quota_exhausted` in the body says why. IPv6 callers are counted per /64, and the service caps its own free allowance at 500 verifications a day across everyone, so a free call is not guaranteed to be available. An unpaid call is free and is how you read the current price. One authorisation buys one verification, and replaying a spent one answers 409 without the verdict it bought: a result is released against a settled payment or not at all. Sign the window offered in `maxTimeoutSeconds`, since an authorisation signed to outlive it is refused. Run one verification at a time per payer. A second in parallel from the same wallet answers 409 `payment_claim_in_flight` and leaves that authorisation unspent, so resend it once the first has finished. Allow 20 seconds: that is the verification's whole wall-clock budget, and exceeding it is a 504 rather than a slower answer. ## Request - `ecosystem`: required, `npm` only. - `name`: required, ASCII, max 214 characters. `@scope/name` accepted. Unicode and homoglyph-confusable names are rejected before any upstream call. npm names are case-sensitive, so `JSONStream` and `jsonstream` are verified as different packages. - `version`: optional, max 64 characters. Omit it to verify the registry's current `latest` rather than one release. ## Response - `verdict`: the worst any check produced. - `reasons[]`: one per finding, with `verdict`, `code`, `kind`, `source`, `detail`, and an optional `data` object carrying that check's raw evidence (advisory IDs, download counts, the matched name). Branch on `code`; `detail` is prose for a human. - `sources`: the upstreams consulted, by name: - `npm_registry`: https://registry.npmjs.org - `osv`: https://api.osv.dev/v1 - `npm_downloads`: https://api.npmjs.org/downloads/point/last-week - `checked_at`: RFC 3339 UTC, when the verification ran, which is not always when you asked for it. A result is reused for 1 hour, keyed on ecosystem, name and exact version (omitting `version` is its own key). Paid calls inside that window buy the stored result with its original `checked_at`, so read that field rather than assuming the checks have just run. ## Verdicts Ordered worst-last: safe < caution < block < does_not_exist. `block` is reserved for high precision: confirmed malice, a critical install-script pattern, or the strict slopsquat conjunction. Name-similarity heuristics cap at `caution`. A package absent from the registry is `does_not_exist`, not `block`. ## The 8 checks Run in the order below, stopping early on a terminal verdict (`does_not_exist`, `block`). A response can therefore carry fewer reasons than there are checks; `sources` lists what was actually consulted. 1. Existence (npm registry, fact): Whether the package exists at all 2. OSV (osv.dev, fact): Advisories: a MAL- report or a malicious-code CWE blocks; any other vulnerability cautions 3. Install script (packument scripts, fact): preinstall / install / postinstall surface 4. Typosquat (npm bulk downloads, heuristic): Edit distance to a popular package name 5. Combosquat (npm bulk downloads, heuristic): A popular name with a generic affix attached 6. Scope (npm bulk downloads, heuristic): An unscoped lookalike of a scoped package, and the reverse dependency-confusion shape 7. Repository (packument repository + downloads, heuristic): Borrowed or unverifiable source provenance 8. Reputation (packument age + downloads, fact): First-publish age, weekly download volume, and publication-timeline anomalies Each reason's `kind` is `fact` (established) or `heuristic` (inference). A heuristic never becomes a fact by agreeing with the others. ## Reason codes The closed set, by the verdict each carries. A check always reports one, including when it finds nothing wrong, so a passing check is visible in `reasons` rather than absent from it. - `does_not_exist`: `package_not_found`, `version_not_found` - `block`: `malicious_package`, `malicious_install_script`, `likely_slopsquat` - `caution`: `registry_security_hold`, `version_removed`, `known_vulnerability`, `install_scripts_present`, `suspicious_install_script`, `possible_typosquat`, `possible_combosquat`, `possible_scope_confusion`, `possible_dependency_confusion`, `repository_impersonation`, `suspicious_publication_timeline`, `dormant_package_resurrected` - `safe`: `package_exists`, `no_known_advisories`, `no_install_scripts`, `install_scripts_not_evaluated`, `no_typosquat_match`, `typosquat_recognized_package`, `typosquat_not_evaluated`, `no_combosquat_match`, `combosquat_recognized_package`, `combosquat_not_evaluated`, `no_scope_confusion`, `scope_recognized_package`, `scope_not_evaluated`, `source_repository_present`, `no_source_repository`, `repository_not_evaluated`, `reputation` A `*_not_evaluated` code means the check could not run and said so instead of guessing. It carries `safe` because it is an absent signal, not a pass. ## Example Request: {"ecosystem": "npm", "name": "crossenv"} Response (200), captured from this service with `data` objects elided: { "ecosystem": "npm", "name": "crossenv", "version": null, "verdict": "block", "reasons": [ {"verdict": "caution", "code": "registry_security_hold", "kind": "fact", "source": "npm_registry", "detail": "..."}, {"verdict": "block", "code": "malicious_package", "kind": "fact", "source": "osv.dev", "detail": "crossenv is affected by a confirmed-malicious advisory (GHSA-c2m4-w5hm-vqjw): crossenv is malware"} ], "sources": { "npm_registry": "https://registry.npmjs.org", "osv": "https://api.osv.dev/v1", "npm_downloads": "https://api.npmjs.org/downloads/point/last-week" }, "checked_at": "2026-08-13T12:34:56Z" } Two reasons rather than 8 because `block` is terminal, and reasons keep their own verdict: the top-level one is the worst of them, never a summary. ## It fails loud If a required data source cannot be reached, the answer is 502 and no verdict, never the remaining checks reported as if complete. `safe` means every check ran and every check passed. ## Errors - 400 `invalid_json`: The request body is not valid JSON. - 402 `payment_identity_missing`: The request reached the origin carrying no payment identity. - 402 `free_quota_exhausted`: Today's free verification has already been used, or the service's daily free allowance is gone. - 404 `not_found`: The request did not arrive through the paid Worker. - 405 `method_not_allowed`: Anything other than POST on /v1/verify. - 409 `payment_already_used`: This authorisation was already spent. One authorisation buys one verification. - 409 `payment_claim_in_flight`: This payer already has a verification running. The authorisation is unspent; resend it. - 422 `invalid_request`: A missing or malformed name, ecosystem, or version. - 422 `unsupported_ecosystem`: An ecosystem other than npm. - 502 `upstream_error`: A required data source could not be reached. No verdict is issued. - 504 `upstream_timeout`: The verification exceeded its wall-clock budget. ## Machine-readable All free. The first three are served identically on both hosts, so fetch them from https://x402.pkgproof.net if that is where you already are. - [OpenAPI 3.1](https://pkgproof.net/openapi.json): full request and response schemas. - [x402 terms](https://pkgproof.net/.well-known/x402): advisory mirror of the 402. The live 402 from https://x402.pkgproof.net/v1/verify wins if the two disagree. - [This file](https://pkgproof.net/llms.txt). - [Human documentation](https://pkgproof.net/docs): the same contract in prose. Apex only; the paid host 404s it, as it does anything not listed here.