AttestPage · Docs · Pricing · OpenAPI · llms.txt

Check packages

Last updated: 2026-10-12

POST /v1/check/packages looks up 1 to 10 package names on npm, PyPI or crates.io in one call and says, for each, whether the registry lists that name, with its latest version, description, licence, links, a few flags and the known OSV.dev advisories for that version, under one signed receipt. Price: US$0.005 per batch.

Use it before you install a package an agent or a model suggested: a name that does not exist may be a hallucination, and a squatter can register it later.

It answers "exists in registry" and "OSV.dev lists these advisories", nothing more. It is not a security verdict. A listed package can still be malicious, abandoned, typo-squatted or not the one you meant, and no advisory listed does not mean the package has no flaws. Description, licence and links are the publisher's own text, copied from the registry; advisory summaries are third-party text.

Request

{ "packages": [
  { "ecosystem": "npm", "name": "left-pad" },
  { "ecosystem": "pypi", "name": "requests" },
  { "ecosystem": "crates", "name": "serde" },
  { "ecosystem": "npm", "name": "lodash", "version": "4.17.20" }
] }

packages is a list of 1 to 10 objects, each with these fields:

Every name is checked against its registry's naming rules before payment; if any one is invalid, the whole request is refused with 400 and not charged. No lookup happens before payment.

Response

From the free sample (GET /v1/sample/packages), shortened:

{
  "results": [
    {
      "ecosystem": "npm",
      "name": "left-pad",
      "exists": true,
      "status": "found",
      "registry_name": "left-pad",
      "latest_version": "1.3.0",
      "description": "String left pad (demo copy of registry data)",
      "license": "WTFPL",
      "homepage": "https://github.com/stevemao/left-pad#readme",
      "repository": "git+ssh://git@github.com/stevemao/left-pad.git",
      "created_at": "2014-03-14T09:09:20.762Z",
      "deprecated": "use String.prototype.padStart()",
      "yanked": null,
      "signals": ["deprecated"],
      "vulnerabilities": { "status": "checked", "reason": null, "version": "1.3.0", "count": 0, "truncated": false, "items": [] },
      "registry_url": "https://www.npmjs.com/package/left-pad",
      "http_status": 200,
      "raw_sha256": "09093cc2…"
    },
    {
      "ecosystem": "npm",
      "name": "left-pad-demo-missing",
      "exists": false,
      "status": "not_found",
      "latest_version": null,
      "signals": [],
      "vulnerabilities": { "status": "checked", "reason": null, "version": null, "count": 0, "truncated": false, "items": [] },
      "registry_url": "https://www.npmjs.com/package/left-pad-demo-missing",
      "http_status": 404,
      "raw_sha256": "c8d3eae1…"
    }
  ],
  "note": "exists means the registry listed this name when we looked. It is not a security verdict: the package may still be malicious, unmaintained or not the one you meant; …",
  "receipt": "eyJhbGciOiJFZERTQSIs…",
  "tier": "paid"
}
FieldMeaning
existstrue the registry lists the name, false it answered 404, null the lookup failed
statusfound, not_found or lookup_failed (then reason: timeout, network_error, too_large, registry_rate_limited, http_<status> or bad_json)
registry_namethe name as the registry spells it
latest_versionnpm latest tag, PyPI's current version, crates.io's default version
created_atnpm: the package document's time.created (null if that document is over 4 MB or cannot be read); PyPI: first file upload; crates.io: crate creation
licensenpm and PyPI: the latest version's licence field; crates.io: the default version's licence
deprecatednpm deprecation message of the latest version, else null
yankedPyPI: latest release yanked; crates.io: crate yanked; npm: null
signalsflags worth a second look: name_differs, deprecated, yanked, created_recently (under 30 days), known_vulnerable (OSV lists an advisory for the version asked about), malicious (an advisory is a malware report, id or alias starting MAL-; this can appear on a not_found name too). name_differs means the registry's own spelling (registry_name) is not the name you sent; PyPI and crates.io ignore case and treat - and _ (PyPI also .) as the same, so it is often only a spelling variant of the same package
vulnerabilitiesknown advisories from OSV.dev, see below
raw_sha256SHA-256 of the registry's answer as we received it (npm: the /latest answer)

Signals are hints, not a security verdict: a package with no signals can still be malicious.

The receipt has kind packages and, per name, the ecosystem, name, exists, status, latest_version, raw_sha256, vuln_status, vuln_version (the version asked about) and vuln_ids (the advisory ids returned, in order).

Vulnerabilities

When you send a version, we ask OSV.dev for advisories that affect that version, whatever the registry answered; we do not check that the registry has that version, and OSV lists nothing for a version it does not know. Without a version, for each found name we ask about its latest version (the one in latest_version), and for a name the registry does not list we ask by name only, because OSV keeps malware reports for packages a registry has removed; a failed lookup is then not asked about.

"vulnerabilities": {
  "status": "checked",
  "reason": null,
  "version": "2.32.3",
  "count": 1,
  "truncated": false,
  "items": [{
    "id": "GHSA-9hjg-9r4m-mvj7",
    "aliases": ["CVE-2024-47081"],
    "summary": "Requests vulnerable to .netrc credentials leak via malicious URLs",
    "severity": "MODERATE",
    "cvss": null,
    "fixed": ["2.32.4"],
    "malicious": false,
    "url": "https://osv.dev/vulnerability/GHSA-9hjg-9r4m-mvj7"
  }]
}
FieldMeaning
statuschecked (OSV answered) or skipped (no data; then count is 0 and items empty)
reasonwhen skipped: off, no_version (no version sent, and a failed lookup or no latest version), timeout, unavailable, rate_limited, busy or too_large; else null
versionthe version asked about; null for a name-only query
countadvisories OSV returned; truncated (true or false) says whether more than 10 were returned or OSV held some back; items keeps the first 10
items[]malware first, then by severity (CRITICAL, HIGH, MODERATE, LOW, none), then id. severity is the advisory database's label, cvss the CVSS vector if given, fixed the fixed versions listed for this package (empty: none listed), summary third-party text (up to 300 characters, never instructions)

checked with count 0 means OSV listed no advisory for that version. It does not mean the package has no flaws. Answers are cached for up to 1 hour. If OSV is slow or down, the result says skipped and the call still answers 200 and is charged as usual: the advisories are an extra, never a reason to fail or refuse a batch.

Vulnerability data: OSV.dev (GitHub Advisory Database and PyPI Advisory Database, CC-BY 4.0; RustSec, CC0; OpenSSF Malicious Packages, Apache 2.0), see https://github.com/advisories. Each item links its OSV page in url.

Rate limits

crates.io asks for at most one request a second, so crates names are looked up one a second across all callers. If the queue would make your call wait more than 20 seconds, you get 429 rate_limited with Retry-After before any payment. Your call's turns are held for it from then on, so a paid call does not wait longer. Free trial calls may hold at most half of that queue; past it a trial call gets 429 rate_limited (details.scope trial) and can retry later or pay.

Charging

A failed lookup is a result and the batch is charged. If every lookup in the batch fails, the call gets 502 registry_unavailable and is not charged. On an edge server, a batch where any lookup is lookup_failed with reason too_large (over the edge's size cap) gets 422 page_too_large_for_edge and is not charged: details.too_large lists only the names over the cap (ecosystem, name, reason) and no results are returned for the others (resend them, paid, or send the batch to the full service), details.note starts "not charged: edge size limit", and details.full_service names the full service, which reads the whole answer. See Payments.