# npm Package Stats & Metadata (`grit-77/npm-packages`) Actor

npm package scraper for dependency and package research. Export registry metadata as JSON with optional download totals or daily download records.

- **URL**: https://apify.com/grit-77/npm-packages.md
- **Developed by:** [Grit](https://apify.com/grit-77) (community)
- **Categories:** Developer tools, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.40 / 1,000 packages

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

## npm Package Stats & Metadata

npm packages scraper returns registry metadata and optional download totals or daily download records for selected packages.

npm packages scraper uses the public source named below and writes its results to the Actor dataset.

Supply package names or official npm URLs, discover packages with registry search, or combine both. Scoped packages are supported. The actor enriches the latest version advertised by the registry and removes duplicate package targets before applying the output cap.

### What it returns

Fields vary by result type and optional enrichment. The examples below are from `sample_output.json`.

| Output field | Type | Example value or excerpt from saved sample |
|---|---|---|
| `package` | string | `"grit-npm-package-that-does-not-exist-20261005"` |
| `status` | string | `"error"` |
| `version` | string or null | `"7.0.0"` |
| `downloads` | integer or null | `183020118` |
| `downloadStart` | string or null | `"2026-09-28"` |
| `downloadEnd` | string or null | `"2026-10-04"` |
| `dailyDownloads` | array or null | `[{"day": "2026-09-28", "downloads": 36084906}]` |
| `rangeTotal` | integer or null | `183020118` |
| `rangeStart` | string or null | `"2026-09-28"` |
| `rangeEnd` | string or null | `"2026-10-04"` |
| `downloadPeriod` | string | `"last-week"` |
| `searchQuery` | string or null | `"keywords:validation"` |
| `searchScore` | number or null | `66.08935` |
| `searchScores` | object or null | `{"popularity": 1}` |
| `sourceUrls` | object | `{"metadata": "https://registry.npmjs.org/grit-npm-package-that-does-not-exist-20261005"}` |
| `errors` | array | `[{"stage": "metadata", "message": "npm returned HTTP 404", "httpStatus": 404}]` |
| `fetchedAt` | string | `"2026-10-05T12:01:51.644217+00:00"` |
| `versionCount` | integer | `15` |
| `distTags` | object | `{"latest": "7.0.0"}` |
| `description` | string | `"Returns true if a number or string value is a finite number. Useful for regex matches, parsing, use…` |
| `license` | string | `"MIT"` |
| `homepage` | string | `"https://github.com/jonschlinkert/is-number"` |
| `repositoryUrl` | string | `"https://github.com/jonschlinkert/is-number"` |
| `npmUrl` | string | `"https://www.npmjs.com/package/is-number"` |
| `createdAt` | string | `"2014-09-22T01:58:51.592Z"` |
| `modifiedAt` | string | `"2023-05-26T16:12:33.057Z"` |
| `publishedAt` | string | `"2018-07-04T15:08:58.238Z"` |
| `maintainers` | array | `["doowb"]` |
| `dependencies` | object | `{}` |
| `dependencyCount` | integer | `0` |
| `devDependencies` | object | `{"ansi": "^0.3.1"}` |
| `peerDependencies` | object | `{}` |
| `optionalDependencies` | object | `{}` |
| `engines` | object | `{"node": ">=0.12.0"}` |
| `deprecated` | null | `null` |
| `tarballUrl` | string | `"https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"` |
| `unpackedSize` | integer | `9615` |

| Fields | Meaning |
|---|---|
| package, status | Canonical name and ok / partial / error. A search failure has package=null. |
| version, versionCount, distTags | Latest tagged version, number of registry versions and tag-to-version map. |
| description, license | Latest version description and declared license; not a legal assessment. |
| npmUrl, homepage, repositoryUrl | Public package and project links. Repository URL removes git+ and a trailing .git. |
| createdAt, modifiedAt, publishedAt | Registry creation/update times and publication time of the latest version. |
| maintainers | Published npm maintainer names; emails are omitted. |
| dependencies, dependencyCount | Latest version runtime dependency map and its size. |
| devDependencies, peerDependencies, optionalDependencies, engines | Other dependency maps and declared runtime requirements. |
| deprecated, unpackedSize, tarballUrl | Deprecation message and latest version distribution metadata, when published. |
| downloads, downloadStart, downloadEnd | Point API total and its inclusive UTC dates; null when omitted/unavailable. |
| dailyDownloads | Range records as {day, downloads}; null when omitted/unavailable. |
| rangeTotal, rangeStart, rangeEnd | Sum of returned daily downloads and the range's inclusive dates. |
| downloadPeriod | Requested named period or explicit custom interval. |
| searchQuery, searchScore, searchScores | Search query, registry score.final and score.detail for discovered packages. |
| sourceUrls, fetchedAt | Requested official source URLs and row collection timestamp. |
| errors | Stage-specific messages with an optional HTTP status. Missing data is not converted to zero. |

Metadata-only failure rows contain the common status/download/provenance fields; registry-specific fields may be absent. Available registry fields may be null or empty maps. A run summary is stored under SUMMARY in the default key-value store.

### Input example

```json
{
  "packages": ["is-number", "@sindresorhus/is"],
  "searchQuery": "keywords:validation",
  "downloadMode": "both",
  "downloadPeriod": "last-week",
  "maxItems": 5,
  "concurrency": 3
}
```

| Input | Default | Behavior |
|---|---|---|
| packages | \[] | Package names or npmjs.com/package URLs; registry.npmjs.org package URLs also work. URL /v/version suffixes still select latest metadata. Plain name@version input is rejected. |
| searchQuery | empty | Official registry full-text search, including npm qualifiers such as keywords:validation. At least packages or a query is required. |
| downloadMode | both | both, point, range, or none. |
| downloadPeriod | last-week | last-day, last-week, last-month, last-year, or custom. |
| startDate, endDate | empty | Required for custom. YYYY-MM-DD; ordered inclusive interval from 2015-01-10 through yesterday UTC, at most 366 days. |
| maxItems | 100 | 1–10,000 output rows, including errors. Explicit packages take priority over discovered packages. |
| concurrency | 5 | 1–20 parallel package workers. Each worker sends one request at a time. |

A named download period refers to npm's last available UTC days. In both mode, the daily range is requested with the point response's actual dates so cached aliases do not silently select different weeks. If point data fails, range is requested independently using the input period. Point and range values retain their separate source fields.

### Real output example

Excerpt of the is-number row from [sample_output.json](sample_output.json), collected on 2026-10-05:

```json
{
  "package": "is-number",
  "status": "ok",
  "version": "7.0.0",
  "license": "MIT",
  "repositoryUrl": "https://github.com/jonschlinkert/is-number",
  "downloads": 183020118,
  "downloadStart": "2026-09-28",
  "downloadEnd": "2026-10-04",
  "rangeTotal": 183020118,
  "rangeStart": "2026-09-28",
  "rangeEnd": "2026-10-04",
  "dailyDownloads": [
    {
      "day": "2026-09-28",
      "downloads": 36084906
    },
    {
      "day": "2026-09-29",
      "downloads": 36365987
    },
    {
      "day": "2026-09-30",
      "downloads": 37177002
    },
    {
      "day": "2026-10-01",
      "downloads": 37568034
    },
    {
      "day": "2026-10-02",
      "downloads": 0
    },
    {
      "day": "2026-10-03",
      "downloads": 18159553
    },
    {
      "day": "2026-10-04",
      "downloads": 17664636
    }
  ],
  "errors": []
}
```

The saved sample contains 5 rows: 4 successful packages and one intentionally nonexistent package error. The actor's local measured processing time was 1.979 seconds; this is one local run, not a throughput or cloud guarantee. Receipts: evidence/sample-run-summary.json and evidence/independent-sample-check.json.

### Pricing

Proposed package event: **$0.0004 per metadata-bearing row ($0.40 per 1,000)**. Download totals and daily records are included. Partial rows retain registry metadata and are charged once. Invalid inputs, metadata failures and discovery error rows are free. No proposed start fee. Publication and actual billing are pending; see [PRICING.md](PRICING.md) for the current leader comparison and required event configuration. Platform resource fees are separate from this proposed event comparison.

### Limits and failure handling

- Metadata and search come from registry.npmjs.org; downloads come from api.npmjs.org. No npm login, browser automation or proxy is required by this implementation.
- Per-request timeout is 30 seconds, with a 10-second connect timeout. Transient transport failures, HTTP 429 and HTTP 500/502/503/504 get three attempts with backoff and a bounded numeric Retry-After delay.
- A malformed or missing package produces an error row. Download failures keep registry metadata as a partial row. Discovery failure produces an error row within the combined cap and keeps explicit packages.
- An empty search with no matches produces an empty dataset. Large registry documents consume memory; reduce concurrency for packages with many versions. Only latest metadata is emitted; full readmes and version manifests are not returned.
- Downloads are package totals, not unique users or per-version usage. They are published with a delay and may be revised by npm. Search scores and ordering are the registry's values, not a security or quality guarantee.
- Output order follows completion order. Deduplication and maxItems apply to the current run. There is no checkpointed continuation after a cloud restart or migration.
- The package charge budget stops further paid rows. Storage or billing failures fail the run rather than being disguised as npm item errors.

### FAQ

**What limits output?** `maxItems` counts paid and free rows together; package targets are deduplicated before output.

**Do I need a proxy or npm login?** No. The actor reads public npm registry and download APIs without a login or configured proxy.

**How is it priced?** The proposed pay-per-event model charges `package` for each metadata-bearing package row, including a partial row when downloads fail. Metadata failures and error rows have no custom package charge.

**How are rate limits handled?** Source requests are retried with bounded backoff, including `Retry-After` when present. Failed download requests can leave download fields null.

**How fresh are package metrics?** `fetchedAt` marks collection. Registry timestamps and the download period describe different source windows; downloads can change with later runs.

**Do daily download records cost extra?** No. Daily records are nested in the package row and do not create another custom event.

### Use with AI agents / MCP

MCP (Model Context Protocol) lets an AI agent call tools. After the owner publishes the actor, expose it through [Apify's MCP integration](https://docs.apify.com/platform/integrations/mcp) or call it using the Apify API. Give the agent the input example above, then read the Results dataset link. Use status, sourceUrls, fetchedAt and errors to decide whether data is suitable for the agent's task. Keep maxItems small during exploration. This package is an actor; it does not contain its own MCP server.

### Local development

Use the project's existing venv; no extra packages were needed. From this actor directory in PowerShell:

```powershell
New-Item -ItemType Directory -Force storage/key_value_stores/default | Out-Null
Copy-Item example_input.json storage/key_value_stores/default/INPUT.json
$env:APIFY_LOCAL_STORAGE_DIR = "$PWD/storage"
C:/GritWork/earn/.venv/Scripts/python.exe -m src
C:/GritWork/earn/.venv/Scripts/python.exe -m pytest tests -m "not live" -q -p no:cacheprovider
C:/GritWork/earn/.venv/Scripts/python.exe -m pytest tests -m live -q -p no:cacheprovider
apify validate-schema
```

Dataset rows are written under storage/datasets/default. In the verified local SDK 4.0.2 runs, a new actor process produced a fresh default dataset and preserved INPUT.json. Copy outputs before another local run if you want to retain the previous dataset. Docker uses apify/actor-python:3.12 and installs the pinned runtime dependencies from requirements.txt.

### Sources

[npm registry API](https://github.com/npm/registry/blob/main/docs/REGISTRY-API.md), [npm download API](https://github.com/npm/registry/blob/main/docs/download-counts.md), [Apify Python SDK](https://docs.apify.com/sdk/python). Real response fixtures and their retrieval URLs are under tests/fixtures.

# Actor input Schema

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

Names (react, @sindresorhus/is) or official npm package URLs. Latest version metadata is returned. Duplicates are removed. Explicit packages have priority over search results.

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

Optional npm registry search, e.g. keywords:validation or scope:apify. Provide packages, a search query, or both.

## `downloadMode` (type: `string`):

Point gives a total, range gives daily downloads, both gives both responses, none skips download requests.

## `downloadPeriod` (type: `string`):

npm uses the last available UTC days: 1, 7, 30 or 365. Choose custom for an inclusive date interval.

## `startDate` (type: `string`):

Required when downloadPeriod is custom. YYYY-MM-DD, on or after 2015-01-10.

## `endDate` (type: `string`):

Required when downloadPeriod is custom. YYYY-MM-DD, no later than yesterday UTC; at most 366 inclusive days.

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

Combined cap across unique explicit packages, search results and error rows. Paid package events cannot exceed this cap.

## `concurrency` (type: `integer`):

Maximum parallel package workers. Each worker sends one HTTP request at a time; search runs before enrichment.

## Actor input object example

```json
{
  "packages": [
    "is-number",
    "@sindresorhus/is"
  ],
  "downloadMode": "both",
  "downloadPeriod": "last-week",
  "maxItems": 100,
  "concurrency": 5
}
```

# Actor output Schema

## `dataset` (type: `string`):

Open the package result rows in the default dataset.

# 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 = {
    "packages": [
        "is-number",
        "@sindresorhus/is"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("grit-77/npm-packages").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 = { "packages": [
        "is-number",
        "@sindresorhus/is",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("grit-77/npm-packages").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 '{
  "packages": [
    "is-number",
    "@sindresorhus/is"
  ]
}' |
apify call grit-77/npm-packages --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,grit-77/npm-packages"
        }
    }
}
```

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/t5ePcz8L1FtuAx3Da/builds/j9Yk9GdZnXBnGKK2D/openapi.json
