# Package Search — npm, PyPI and crates.io in One Shape (`dalbian/package-registry-search-npm-pypi-crates`) Actor

Search and profile open-source packages across npm, PyPI and crates.io and get them back in one common shape: licence, repository, maintainers, first and last release, release count, download figures, dependent count, deprecation and yank status, and runtime dependencies. Optional.

- **URL**: https://apify.com/dalbian/package-registry-search-npm-pypi-crates.md
- **Developed by:** [Benjamin Jerez](https://apify.com/dalbian) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 search runs

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Package Search — npm, PyPI and crates.io in One Shape

Search or look up open-source packages across the three largest language registries and get them back as **one row shape**, so a JavaScript package, a Python project and a Rust crate line up in the same spreadsheet column.

No API key, no scraping, no login. Three public registry APIs, queried live.

### What a row looks like

One run of the default input — the term `websocket` against all three registries, plus the direct lookups `npm:express`, `pypi:requests`, `crates:serde` — returned 44 packages and 213 version rows in 12 seconds on 19 September 2026.

| Field | `express` (npm) | `requests` (PyPI) | `serde` (crates.io) |
| --- | --- | --- | --- |
| `version` | 5.2.1 | 2.34.2 | 1.0.229 |
| `license` | MIT | Apache-2.0 | MIT OR Apache-2.0 |
| `weeklyDownloads` | 121 351 312 | — | — |
| `monthlyDownloads` | 482 331 266 | — | — |
| `totalDownloads` | — | — | 1 412 337 605 |
| `recentDownloads90d` | — | — | 314 361 134 |
| `downloadsBasis` | npm search API, last 7 and last 30 days | none — PyPI publishes no download counts in its JSON API | crates.io, all-time and last 90 days |
| `dependentsCount` | 108 010 | — | 123 264 |
| `releaseCount` | 289 | 163 | 316 |
| `publishedAt` | 2010-12-29 | 2011-02-14 | 2014-12-05 |
| `lastPublishedAt` | 2026-09-18 | 2026-05-14 | 2026-07-18 |
| `runtimeDependencyCount` | 28 | 4 | — |
| `repositoryUrl` | github.com/expressjs/express | github.com/psf/requests | github.com/serde-rs/serde |

Every row also carries `description`, `homepage`, `documentationUrl`, `author`, `maintainers`, `keywords`, `categories`, `licenseRaw`, `licenseSource`, `runtimeRequirement`, `packageUrl`, `isDeprecated`, `deprecationMessage`, `isYanked`, `yankedReason`, and the per-version counts `deprecatedVersionCount` and `yankedVersionCount`.

Each query produces a `summary` row first, before its packages: the registry, the term, how many the registry says match, how many were returned, and a `note` explaining any zero.

Turning on **Include version history** adds one `version` row per release, newest first, with its publication date, licence, size, and yank or deprecation status.

### The three registries are not the same, and the output says so

The point of a common shape is that it does not hide the differences. Where a registry cannot answer, the field is empty and a `note` or `downloadsBasis` field says why, rather than a plausible number being invented.

**PyPI cannot be searched.** There is no JSON search endpoint. `pypi.org/search` returns HTML, and the only machine-readable index, `/simple/` as JSON, is a 44 MB list of bare project names with no metadata and no ranking. A search term is therefore resolved against PyPI as an exact project name and nothing more; the summary row says so on every run. PyPI does its own name normalisation, so `scikit_learn`, `Scikit-Learn` and `scikit-learn` all reach the same project. For anything other than an exact name, use the direct-lookups field.

**Download figures come from different clocks.** npm publishes last-7-day and last-30-day counts inline on its search endpoint. crates.io publishes an all-time total and a 90-day recent figure — verified against crates.io's own daily download series, which summed to 314 481 694 over 2026-06-22 to 2026-09-19 against a reported `recent_downloads` of 314 361 134. PyPI publishes nothing: its `info.downloads` object has returned `-1` for years. The `downloadsBasis` field states which clock each row used.

**Deprecated npm packages have no download figure here.** npm's download counts are only reachable through its search endpoint, and npm search excludes deprecated packages entirely — `request` and `left-pad` do not appear in their own search results even at the maximum page size of 250. Those rows carry an explicit `downloadsNote` instead of a guess.

**Deprecation and yanking are not the same thing, and not every registry has both.**

| | npm | PyPI | crates.io |
| --- | --- | --- | --- |
| Deprecation | Per version. `isDeprecated` means the version in `version` (the `latest` dist-tag) carries a message — the same thing the npm CLI warns about. `deprecatedVersionCount` gives the rest. | No such concept. `isDeprecated` is empty, not `false`. | No such concept. Crates signal it in prose or in a build suffix such as `0.9.34+deprecated`, which is not a flag. Empty. |
| Yanking | No such concept. Empty. | Per release file. `isYanked` is PyPI's own flag for the current version; a release counts as yanked when all of its files are. `requests` has two. | Per version. `isYanked` is true when the current version is yanked or the whole crate is. |

Because the meanings differ, empty and `false` are kept distinct: empty means the registry has no such concept, `false` means it has one and this package is not flagged.

**Licences are normalised, and the raw value is kept beside them.** `license` is one comparable string; `licenseRaw` is exactly what the registry returned; `licenseSource` says which field it came from. npm still serves all three historical shapes — a plain string, an object (`sax`, `nopt`, `semver`), and a `licenses` array of objects (`jsdom`, `connect`, `npm` itself) — and all three are read. PyPI is resolved in order of authority: the PEP 639 `license_expression` first (numpy has `BSD-3-Clause AND 0BSD AND MIT AND Zlib AND CC0-1.0` there and leaves `license` empty), then the free-text `license` field when it is short enough to be an identifier rather than a pasted licence text, then the `License ::` classifiers mapped onto SPDX. crates.io states an SPDX expression per version, so the package licence is the current version's and is not merged across history.

**crates.io requires a descriptive User-Agent.** Without one it returns HTTP 403 with an empty body. This Actor identifies itself as required by crates.io's published crawler policy.

### Limits the registries impose

- npm accepts at most 250 results per search page; asking for more is silently clamped.
- crates.io accepts at most 100 per page and refuses to page past the first 1000 relevance-ranked results, so 1000 is the hard ceiling per crates.io query. The summary row says so when you ask for more.
- npm treats a multi-word query as OR over the words and ranks the union. The query `zzzzz nonexistent term xyz` reports 14 715 matches and returns `verror` and `@coral-xyz/anchor`, none of which contain any of those words as written. A single-token query is exact: `qqwwzzxxjjvv` returns zero. The summary row carries a warning whenever a query has more than one token, and the `totalMatching` figure for such a query is a union, not an intersection.
- crates.io returns a true zero for a term that matches nothing.

### Size, and what is capped

npm packuments are the one genuinely large object involved. Measured on 19 September 2026: `typescript` 15.7 MB over 3828 versions, `@types/node` 11.2 MB over 2373, `react` 7.0 MB over 2953. Anything past **24 MB** is abandoned mid-stream rather than held in memory, and the package is rebuilt from its small `/latest` manifest with a `note` saying that release count, first-release date and version history are unavailable for it.

The abbreviated packument (`Accept: application/vnd.npm.install-v1+json`) was tested and rejected as the default. It is genuinely smaller — `typescript` drops to 8.7 MB, `react` to 2.9 MB — but it carries only `name`, `version`, `dist`, `dependencies`, `engines` and `bin` per version. It has no licence, no repository, no description, no maintainers and, decisively, no `time` map, so first-release and last-release dates cannot be derived from it at all. The full packument is fetched instead, streamed and capped.

Version history is capped by **Maximum versions per package**, default 25, newest first, because a full history of `typescript` alone would be 3828 rows.

### Who uses this

Engineering and platform teams building an internal catalogue of approved dependencies across several languages at once. Security and licence-compliance work that needs the licence, the repository and the deprecation or yank status of the same package name in three ecosystems. Developer-tool and package-explorer products that want registry-agnostic package cards. Analysts sizing an ecosystem — how many crates match a term, how many are maintained, how the download curve compares. Teams naming a new package who want to see where a name is already taken: put the bare name in the direct-lookups field and it is tried in every registry you selected.

### Scope and limits

**Current state only.** Every field is what the registry says today. Download figures are not backfilled and no historical series is produced.

**Dependencies are runtime dependencies of the current version.** Build, test and development dependencies are excluded, as are PyPI dependencies gated behind an extra, so what you get is what installs alongside the package. crates.io keeps dependencies on a separate per-version endpoint that would double the request count per crate, so crate rows carry a `dependenciesNote` instead of a list.

**Dependent counts are not comparable between registries.** npm counts dependent packages as its search index reports them; crates.io counts reverse dependencies from its own endpoint; PyPI publishes no such figure.

**PyPI maintainer accounts are not exposed** by the JSON API. The `maintainers` field for a PyPI row carries the `Maintainer` metadata field from the package itself, which is often a person's name rather than the PyPI accounts shown on the project page.

**Deduplication is by registry and name together.** The same name on npm and on PyPI is two packages and is returned twice, which is usually the point. The same package reached by two queries in one run is returned once.

### Source and licence

**npm** — `registry.npmjs.org`, the public CouchDB registry and its search endpoint. **PyPI** — `pypi.org/pypi/<name>/json`, the Python Package Index JSON API. **crates.io** — `crates.io/api/v1`, the official Rust crate registry API.

All three are free, keyless and public. The metadata is published by the package authors under each registry's terms of use; licence information is reported as the authors declared it and is not legal advice. Queried live on every run, with no cached copy in between.

### Frequently asked

**Why is there no download figure for a Python package?**
Because PyPI does not publish one. Its JSON API has returned `-1` for the download counters for years, and the third-party service that fills the gap is not used here. The `downloadsBasis` field says this on every PyPI row.

**Why did my search return nothing on PyPI but plenty on npm?**
PyPI has no search endpoint. The term was tried as an exact project name and did not match one. Look the package up by its exact PyPI name in the direct-lookups field.

**Why does an npm search for a phrase return packages that have nothing to do with it?**
npm matches each word separately and ranks the union. Use a single word, and read the `note` on the summary row, which flags this on every multi-word query.

**Does `isDeprecated` being empty mean the package is fine?**
No. Empty means the registry has no deprecation mechanism at all — PyPI and crates.io do not. `false` means npm has one and this package's current version is not flagged.

**A crate shows version 0.5.6 but crates.io's API says `max_version` is 0.0.0. Which is right?**
Both. `max_version` reads `0.0.0` once every version of a crate has been yanked, which is the case for `whatfeatures`. The `version` field reports `default_version`, the one cargo would actually resolve to, and `isYanked` is true.

**How many packages can one run handle?**
Searches are capped per query per registry by **Maximum results per query**, up to 500, and the registries' own ceilings apply on top — 1000 for a crates.io relevance search. Direct lookups are unlimited. Registry requests run concurrently, with crates.io throttled to honour its published crawler policy.

**Is a query that finds nothing charged?**
A search run is charged once per registry per query whether it matches or not, because the query was still run. Packages are charged only when returned, so a zero-result query costs one search run and nothing else.

# Actor input Schema

## `queries` (type: `array`):

One free-text search per line, run against every registry you select below. Use a single word where you can: npm treats a multi-word query as OR over the words and will return popular packages that match only one of them, so 'websocket' is a sharper query than 'fast websocket client'. PyPI has no JSON search endpoint at all — a term here is resolved against PyPI as an exact project name and nothing else. See the README for the detail.

## `packages` (type: `array`):

Known packages to profile, one per line, written as registry:name — npm:express, pypi:requests, crates:serde. Scoped npm names work as npm:@types/node. A bare name with no prefix is looked up in every registry you selected below, which is the quick way to see where a name is taken.

## `registries` (type: `array`):

Which registries to query. Leaving all three on is what makes the output comparable side by side; turn PyPI off if you only ever search, since PyPI cannot be searched.

## `maxResultsPerQuery` (type: `integer`):

Cap on packages returned per search term per registry. npm pages 250 at a time and crates.io 100; crates.io refuses to page past the first 1000 relevance-ranked results, so 1000 is the hard ceiling there.

## `includeVersionHistory` (type: `boolean`):

Add one row per released version with its publication date, licence, and yank or deprecation status. Off by default because popular packages carry thousands of versions — TypeScript alone has more than 3800.

## `maxVersions` (type: `integer`):

Cap on version rows per package, newest first.

## `includeDependencies` (type: `boolean`):

List the runtime dependencies the current version declares. Build, test and development dependencies are excluded, as are PyPI extras, so the list is what actually gets installed alongside the package.

## Actor input object example

```json
{
  "queries": [
    "websocket"
  ],
  "packages": [
    "npm:express",
    "pypi:requests",
    "crates:serde"
  ],
  "registries": [
    "npm",
    "pypi",
    "crates"
  ],
  "maxResultsPerQuery": 20,
  "includeVersionHistory": false,
  "maxVersions": 25,
  "includeDependencies": true
}
```

# Actor output Schema

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "queries": [
        "websocket"
    ],
    "packages": [
        "npm:express",
        "pypi:requests",
        "crates:serde"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("dalbian/package-registry-search-npm-pypi-crates").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "queries": ["websocket"],
    "packages": [
        "npm:express",
        "pypi:requests",
        "crates:serde",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("dalbian/package-registry-search-npm-pypi-crates").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "queries": [
    "websocket"
  ],
  "packages": [
    "npm:express",
    "pypi:requests",
    "crates:serde"
  ]
}' |
apify call dalbian/package-registry-search-npm-pypi-crates --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dalbian/package-registry-search-npm-pypi-crates"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/zXNhMUf7lJS0pCcQq/builds/Gr8Vpa2Iak7kiKMyR/openapi.json
