# Check packages

Last updated: 2026-10-10

`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 and a few flags, 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", nothing more. It is not a security verdict.** A listed package can still be malicious, abandoned, typo-squatted or not the one you meant. Description, licence and links are the publisher's own text, copied from the registry.

## Request

```json
{ "packages": [
  { "ecosystem": "npm", "name": "left-pad" },
  { "ecosystem": "pypi", "name": "requests" },
  { "ecosystem": "crates", "name": "serde" }
] }
```

`packages` is a list of 1 to 10 objects, each with exactly two fields:

- `ecosystem`: `npm`, `pypi` or `crates` (crates.io)
- `name`: the package name as you would install it. npm names may be scoped (`@scope/name`).

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:

```json
{
  "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"],
      "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": [],
      "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"
}
```

| Field | Meaning |
|---|---|
| `exists` | `true` the registry lists the name, `false` it answered 404, `null` the lookup failed |
| `status` | `found`, `not_found` or `lookup_failed` (then `reason`: `timeout`, `network_error`, `too_large`, `registry_rate_limited`, `http_<status>` or `bad_json`) |
| `registry_name` | the name as the registry spells it |
| `latest_version` | npm `latest` tag, PyPI's current version, crates.io's default version |
| `created_at` | npm: 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 |
| `license` | npm and PyPI: the latest version's licence field; crates.io: the default version's licence |
| `deprecated` | npm deprecation message of the latest version, else null |
| `yanked` | PyPI: latest release yanked; crates.io: crate yanked; npm: null |
| `signals` | flags worth a second look: `name_differs`, `deprecated`, `yanked`, `created_recently` (under 30 days). `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 |
| `raw_sha256` | SHA-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` and `raw_sha256`.

## 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](/docs/payments).
