# npm Package Metadata API: PyPI & crates.io, $2/1K (`conserving_celerytop/package-registry-metadata-api`) Actor

Look up npm, PyPI and crates.io packages by name or keyword. One flat row per package with latest version, license, dependencies, release dates, download counts and links. Public registry APIs, $2.00 per 1,000 packages.

- **URL**: https://apify.com/conserving_celerytop/package-registry-metadata-api.md
- **Developed by:** [Don Mangu](https://apify.com/conserving_celerytop) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 package records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## npm Package Metadata API: PyPI & crates.io, $2/1K

Package metadata API for npm, PyPI and crates.io that returns one flat row per package: latest version, license, dependencies, release dates, download counts and links, ready for a spreadsheet, a database or an AI agent. List package names or type a keyword, set a maximum and run. Results cost $2.00 per 1,000 packages, and the three-package sample run costs less than one cent. I built this Actor as an independent developer. It is not affiliated with or endorsed by npm, the Python Software Foundation, PyPI or crates.io.

### Sample output

One row from an npm package metadata lookup for `express`. Values were read from the registry on 2026-10-04 and change over time. Dependencies are cut to the first two for space.

```json
{
  "registry": "npm",
  "name": "express",
  "inputName": "express",
  "status": "ok",
  "message": null,
  "url": "https://www.npmjs.com/package/express",
  "latestVersion": "5.2.1",
  "latestReleaseDate": "2025-12-01T20:49:43.268Z",
  "firstReleaseDate": "2010-12-29T19:38:25.450Z",
  "versionCount": 289,
  "license": "MIT",
  "description": "Fast, unopinionated, minimalist web framework",
  "keywords": ["express", "framework", "sinatra", "web", "http"],
  "homepageUrl": "https://expressjs.com/",
  "repositoryUrl": "https://github.com/expressjs/express",
  "documentationUrl": null,
  "deprecated": false,
  "deprecationMessage": null,
  "runtimeRequirement": "node >= 18",
  "packageSizeBytes": 75429,
  "downloadsWeekly": 165798136,
  "downloadsMonthly": 566208091,
  "downloadsLast90Days": null,
  "downloadsTotal": null,
  "dependentsCount": 108273,
  "dependencyCount": 28,
  "optionalDependencyCount": 0,
  "devDependencyCount": 16,
  "peerDependencyCount": 0,
  "dependencies": [
    { "name": "accepts", "range": "^2.0.0", "kind": "runtime", "condition": null },
    { "name": "body-parser", "range": "^2.2.1", "kind": "runtime", "condition": null }
  ],
  "sourceUrl": "https://registry.npmjs.org/express",
  "fetchedAt": "2026-10-04T07:50:26.667Z"
}
```

### How to get npm, PyPI and crates.io package metadata

1. Open the Actor and add your **Package names**. Write one name per entry, for example `express`, `@types/node` or `django`.
2. Put `npm:`, `pypi:` or `crates:` in front of a name to pick its registry, for example `pypi:requests` or `crates:serde`. Names without a prefix use the registries under **Advanced options** (npm by default).
3. Or type **Search text**, for example `http client`, to find packages on npm and crates.io. Found packages are added after your listed names.
4. Set **Maximum packages**. The prefill is 10, so the first run is cheap. One run returns up to 1,000 rows.
5. Click Start. Rows appear in the dataset as they are saved. Use the Overview table, or export to JSON, CSV or Excel.

You can also call the Actor from code through the Apify API. The example below checks six packages from three registries.

```bash
curl -X POST "https://api.apify.com/v2/acts/conserving_celerytop~package-registry-metadata-api/run-sync-get-dataset-items" \
  -H "Authorization: Bearer <YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"packageNames": ["npm:react", "npm:next", "pypi:django", "pypi:fastapi", "crates:tokio", "crates:axum"], "maxItems": 6}'
```

### What package metadata you get

| Field | Meaning |
|---|---|
| `registry`, `name`, `inputName`, `url` | The registry, the package name as the registry spells it, the name you typed, and the package page |
| `status`, `message` | `ok`, `not_found` or `error`, with a short message when the row is not `ok` |
| `latestVersion`, `latestReleaseDate`, `firstReleaseDate`, `versionCount` | Newest version, when it was published, when the package first appeared, and how many versions exist |
| `license` | The license as the package declares it, usually an SPDX id such as `MIT` or `MIT OR Apache-2.0` |
| `description`, `keywords` | The short description and up to 30 keywords |
| `homepageUrl`, `repositoryUrl`, `documentationUrl` | Links the package lists, with git addresses turned into plain https links |
| `deprecated`, `deprecationMessage` | npm deprecation notice, a yanked release, or a crates.io yanked version |
| `runtimeRequirement` | Minimum runtime the package states: `node`, `python` or `rust` |
| `packageSizeBytes` | npm unpacked size, crate file size, or the PyPI source archive size |
| `downloadsWeekly`, `downloadsMonthly` | npm downloads in the last 7 and 30 days |
| `downloadsLast90Days`, `downloadsTotal` | crates.io downloads in the last 90 days and in total |
| `dependentsCount` | Number of npm packages that depend on this one |
| `dependencyCount`, `optionalDependencyCount`, `devDependencyCount`, `peerDependencyCount`, `dependencies` | Counts and the list of runtime dependencies of the latest version, each with name, version range, kind and condition |
| `sourceUrl`, `fetchedAt` | The registry address that was read and the time of the read |

Each registry publishes different download windows, so a field the registry does not offer is `null`. PyPI publishes no download counts through its JSON API, so its download fields are always `null`.

Author and maintainer names and e-mail addresses are never read or saved.

### Pricing

This Actor uses pay per event. One event is one package row saved to the dataset.

| Plan | Price per package | Price per 1,000 packages |
|---|---|---|
| Free | $0.0020 | $2.00 |
| Bronze | $0.0018 | $1.80 |
| Silver | $0.0016 | $1.60 |
| Gold | $0.0014 | $1.40 |

There is also a start fee of $0.00005 per run.

Worked example: a license audit of 500 packages on the Free plan saves 500 rows, which is 500 x $0.002 = $1.00, plus $0.00005 for the start. The three-package sample run costs 3 x $0.002 = $0.006 plus the start. A name that is not found is saved as one `not_found` row and counts as one package. A run that stops because the registry is down saves an `error` row and is not charged for it. You can set a maximum charge for a run, and the Actor stops when it is reached.

### Input example

```json
{
  "packageNames": ["npm:express", "pypi:requests", "crates:serde"],
  "maxItems": 10
}
```

Search by keyword on npm and crates.io:

```json
{
  "searchQuery": "http client",
  "registries": ["npm", "crates"],
  "maxItems": 40
}
```

### Output example

A crates.io row for `serde` (shortened): `latestVersion` 1.0.229, `license` "MIT OR Apache-2.0", `firstReleaseDate` 2014-12-05, `downloadsTotal` 1,478,260,695, `downloadsLast90Days` 340,421,315, `dependencyCount` 1 (`serde_core`), `optionalDependencyCount` 1. A PyPI row for `requests` has `latestVersion` 2.34.2, `license` "Apache-2.0", `runtimeRequirement` "python >=3.10", `versionCount` 160 and `null` download fields.

### What you can do with package metadata

- Audit the licenses of the packages your team uses, across JavaScript, Python and Rust in one table.
- Track new releases and deprecated packages by running the Actor on a schedule and comparing rows.
- Build a dependency inventory with version ranges for each package.
- Compare package age, release count and download counts when you choose between libraries.
- Feed registry facts to an AI agent or a dashboard through the Apify API.

### Run it every day

Switch on **Only new since the last run** and the Actor saves a row only for a package that has a version it has not delivered to you before. Use it to watch a list of packages for new releases.

1. Fill the input with your package names. Set **Maximum packages** to at least the number of rows you expect to save in one run.
2. Switch on **Only new since the last run** and enter a **Monitor name**, for example `my-stack`. Use one name per saved task.
3. Click **Save as task** and run it once. The first run saves one row for every package and becomes the baseline.
4. In the Apify Console, open **Schedules**, click **Create new**, pick your Task, and enter a cron expression for once a day, for example `0 6 * * *`.

Each later run looks up every package, then saves only the rows whose `latestVersion` is new. Rows skipped as already delivered are not charged. In this mode **Maximum packages** caps the rows saved, not the names looked up, so a long list is never cut. If more rows are new than the maximum, the rest come with the next run. A name that is not found is saved once. A row for a registry that could not be read is saved without a charge and is not remembered, so the next run tries that package again.

The memory lives in your own key-value store, named `pkg-monitor-<monitor name>`. It holds ids only, such as `npm|express|5.1.0`: registry, package name and version. If you change the registries or the search text, the Actor starts a separate memory for that setting, and its first run saves every package again. You can add and remove names without losing the memory.

Worked cost example on the Free plan, with the prices in the Pricing section ($0.002 per row, $0.00005 for the start). Say you watch 200 packages and on a day 6 of them release a new version. That day costs 6 x $0.002 = $0.012 plus $0.00005, so $0.01205. Thirty such days cost 30 x $0.01205 = $0.3615. The first run costs 200 x $0.002 = $0.40 plus the start fee. A day with no new version costs only the start fee of $0.00005. The same 200 packages run daily without Only new save 200 rows every day: 200 x $0.002 = $0.40 plus $0.00005, so $0.40005 a day and 30 x $0.40005 = $12.0015 in 30 days. The 6 releases per day is an example, not a measured value.

### Bulk lists

You can give the Actor a long list of package names from a file or a Dataset instead of typing them. You can use the typed list, a Dataset and a file at the same time. The Actor joins them and removes duplicates.

- **Dataset with the list**: pick an Apify Dataset in the Console, or paste its ID. In **Field in the dataset**, enter the name of the field that holds the package name, for example `package`. Entries can carry a prefix, for example `pypi:requests`.
- **List file URL**: the web address of a .txt, .csv or .json file. A .txt file has one name per line. A .csv file uses the first column. A .json file is an array of strings or objects. The first row is skipped when it is a header such as name, id or package. The file can be up to 10 MB.

Up to 1,000 names per run, as with typed names. The price is the same: $0.002 per package row on the Free plan. A name that is not found is one row, as before. Without Only new, **Maximum packages** still limits the run (the default is 20), so raise it to the number of names in your list. If it is lower, the run message says how many names were left out.

### Related Actors

- [Tech Stack Detector](https://apify.com/conserving_celerytop/website-tech-stack-detector) finds the technologies a website uses.
- [Software Engineer Jobs](https://apify.com/conserving_celerytop/software-engineer-jobs) searches live developer job listings.

### FAQ

#### Where does the package metadata come from?

From the public APIs of the three registries: `registry.npmjs.org` and its search endpoint for npm, `pypi.org/pypi/{name}/json` for PyPI, and the crates.io API plus the crates.io sparse index for crates.io. The Actor uses no login, no cookies, no proxy and no browser.

#### Is it legal to read this data?

The Actor reads public package metadata through the registries' documented APIs. The npm Open Source Terms (last updated March 10, 2022) say: "You may search for, download, publish, and manage Packages using software other than CLI via application programming interfaces that npm publicly documents or makes available for public use." The PyPI Acceptable Use Policy says: "Scraping does not refer to the collection of information through our API." The crates.io data access page offers its API on two conditions, "A maximum of 1 request per second" and "A user-agent header that identifies your application", and this Actor follows both. The same page asks clients to try the sparse index first, and the Actor reads dependencies from it. The Actor saves no personal data about people: author and maintainer names and e-mail addresses are dropped. Package licenses apply to the packages themselves, so check them before you reuse package contents. This is not legal advice.

#### How fast is it, and what are the limits?

Each registry has its own pace. crates.io is read at most once per 1.1 seconds, PyPI at about 1.4 requests per second, and npm at a light pace with three lookups at a time. In practice 1,000 npm packages take about three minutes, 1,000 PyPI packages about 12 minutes and 1,000 crates about 18 minutes. One run saves up to 1,000 rows. Run the Actor again for more.

#### Why are the download fields empty for some rows?

Each registry offers different numbers. npm gives 7 and 30 day downloads and a dependents count. crates.io gives total and 90 day downloads. PyPI gives none through its JSON API. For a few npm packages, such as deprecated ones, the npm search does not list the package, and the Actor then asks the npm downloads API. If that also has no answer, the fields stay `null`.

#### Why do I get a `not_found` row?

The registry has no package with that name, or the npm package was unpublished. Check the spelling and the registry prefix. A name without a prefix is looked up in the registries you chose under Advanced options.

#### What does an `error` row mean?

The registry did not answer after three tries, for example because of an outage. The row says so, and it is not charged. Run the Actor again later. If a registry fails five times in a row, the Actor stops reading that registry and ends the run with the rows it has saved.

#### Troubleshooting

- The run ended at once with "Input needs a change": the message names the entry to fix, such as a name with a space or a maximum above 1,000.
- A crates.io run is slower than an npm run: crates.io asks clients to stay at one request per second.
- A run stopped with the status `charge_limit`: the maximum charge you set was reached, and the saved rows are complete.
- A run was interrupted and restarted: it continues from the saved state and does not save or charge a package twice.

If this saved you time, a rating helps others find it.

# Actor input Schema

## `packageNames` (type: `array`):

Add one package name per entry, for example express or @types/node. Put npm:, pypi: or crates: in front of a name to choose its registry, for example pypi:requests. Names without a prefix use the registries chosen under Advanced options.

## `listDatasetId` (type: `string`):

Pick an Apify Dataset that holds your list, one entry per item. It is added to the entries typed above.

## `listDatasetField` (type: `string`):

Name of the dataset field that holds the entry. Leave empty to use the first text field it finds.

## `listFileUrl` (type: `string`):

Web address of a .txt, .csv or .json file with one entry per line. A header row is skipped. Up to 10 MB.

## `searchQuery` (type: `string`):

Type a keyword to find packages on npm and crates.io, for example http client. Found packages are added after the names you listed.

## `maxItems` (type: `integer`):

Set how many package rows to save, from 1 to 1,000. Each saved row is one charged record, and a name that is not found is one row.

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

Choose the registries for names without a prefix and for search text. A name is looked up once in every chosen registry.

## `onlyNew` (type: `boolean`):

Return only items that this monitor has not delivered before. The first run returns everything and becomes the baseline. Use it with a daily schedule.

## `monitorName` (type: `string`):

Names the memory of this monitor. Use one name per saved task, so two tasks never share their memory.

## Actor input object example

```json
{
  "packageNames": [
    "npm:express",
    "pypi:requests",
    "crates:serde"
  ],
  "maxItems": 10,
  "registries": [
    "npm"
  ],
  "onlyNew": false,
  "monitorName": "default"
}
```

# Actor output Schema

## `packages` (type: `string`):

No description

## `stats` (type: `string`):

No description

# 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 = {
    "packageNames": [
        "npm:express",
        "pypi:requests",
        "crates:serde"
    ],
    "maxItems": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("conserving_celerytop/package-registry-metadata-api").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 = {
    "packageNames": [
        "npm:express",
        "pypi:requests",
        "crates:serde",
    ],
    "maxItems": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("conserving_celerytop/package-registry-metadata-api").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 '{
  "packageNames": [
    "npm:express",
    "pypi:requests",
    "crates:serde"
  ],
  "maxItems": 10
}' |
apify call conserving_celerytop/package-registry-metadata-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,conserving_celerytop/package-registry-metadata-api"
        }
    }
}
```

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/terQCblNc5Ln26mo2/builds/y7vc6uKnvTD0tqZeQ/openapi.json
