# PyPI Package Scraper — Downloads, Releases, Vulnerabilities (`yadroo/pypi-package-intel`) Actor

Python package intelligence for AI agents and due diligence: downloads (day/week/month, by Python version and OS), release history and cadence, parsed requirements, license (PEP 639), development status, known vulnerabilities (OSV), owners, project URLs.

- **URL**: https://apify.com/yadroo/pypi-package-intel.md
- **Developed by:** [Samat Makatov](https://apify.com/yadroo) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.40 / 1,000 result items

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

## PyPI Package Intel — downloads, versions, requirements, vulnerabilities

One clean JSON per Python package: downloads (day/week/month, by Python version and OS), release history and cadence, parsed requirements, normalized license, development status, known vulnerabilities, owners and project links. Built for AI agents, dependency due diligence and developer-tool market research. Plus two feed modes that watch PyPI for new releases and new projects in your niche. No API key, no proxy, no browser.

### Use cases

- **Dependency due diligence** — before adopting a library, get license (PEP 639 expression, field or classifier), maintenance cadence, wheel availability, Python support, owners and open vulnerabilities in one item.
- **Supply-chain / security review** — pin `name==version` to see the OSV advisories (CVE/GHSA aliases, fixed-in) for exactly the version you ship; flag yanked releases.
- **Competitor & market tracking** — compare monthly downloads, Python 3.x adoption and OS split of your SDK vs. alternatives; schedule weekly and diff.
- **Abandonment alerts** — `daysSinceLastRelease`, `releasesLast365d`, `developmentStatus` (Inactive/Mature) feed a "stale dependency" monitor.
- **Lead generation for dev tools** — `newPackages` + `keywords` (e.g. `mcp`, `agent`, `llm`) surfaces projects launching in your category, with author e-mail when published.
- **Release monitoring** — `recentUpdates` + `keywords` catches new versions of an ecosystem (e.g. `langchain`, `openai`) minutes after upload.

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `mode` | string | `packages` | `packages` · `recentUpdates` · `newPackages` |
| `packages` | string\[] | — | Project names; `name==1.2.3` or `name@1.2.3` pins a version. Names are PEP 503-normalized (case/`-`/`_`/`.` insensitive). Required in `packages` mode. |
| `keywords` | string\[] | `[]` | Feed modes only: keep items whose name/summary contains any keyword (case-insensitive). |
| `maxItems` | integer | `100` | Feed modes only (1–200). The PyPI feeds carry the latest ~40 entries. |
| `includeDownloads` | boolean | `true` | Day/week/month downloads from pypistats.org (mirrors excluded). |
| `downloadsBreakdown` | boolean | `false` | Adds `downloadsByPythonMinor30d` and `downloadsBySystem30d` (last 30 days). +2 pypistats requests per package. |
| `downloadsHistoryDays` | integer | `0` | 0–180. Adds `downloadsHistory` daily series + total. +1 request. |
| `includeMirrors` | boolean | `false` | Count mirror traffic in breakdown/history. |
| `includeVulnerabilities` | boolean | `true` | `vulnerabilities[]` from PyPI (OSV). `vulnerabilityCount` always present. |
| `includeReleases` | boolean | `true` | `recentVersions[]` (newest first). |
| `versionsLimit` | integer | `10` | 0–200 entries in `recentVersions`. |
| `includeClassifiers` | boolean | `true` | Raw Trove `classifiers[]`. |
| `descriptionChars` | integer | `0` | 0 = omit; N = first N chars of the long description (README). |
| `fields` | string\[] | `[]` | Whitelist of top-level output fields; `id` and `name` are always kept. |

Existing inputs (`packages`, `versionsLimit`) keep their old meaning.

### Reference

#### Sources and what they provide

| Source | Endpoint | Used for |
|---|---|---|
| PyPI JSON API | `https://pypi.org/pypi/{name}/json`, `/pypi/{name}/{version}/json` | metadata, releases, files, vulnerabilities, ownership |
| PyPI RSS | `https://pypi.org/rss/updates.xml`, `/rss/packages.xml` | `recentUpdates`, `newPackages` modes |
| pypistats.org | `/api/packages/{name}/recent`, `/python_minor`, `/system`, `/overall` | downloads (updated once a day, 180-day depth) |

PyPI has **no search API** (XML-RPC search was disabled in 2021); use the feed modes with `keywords`, or a search engine, to discover names.

#### Development Status classifiers → `developmentStatus`

| Code | Label |
|---|---|
| 1 | Planning |
| 2 | Pre-Alpha |
| 3 | Alpha |
| 4 | Beta |
| 5 | Production/Stable |
| 6 | Mature |
| 7 | Inactive |

#### Trove classifier groups (894 classifiers as of Sep 2026 — full list: https://pypi.org/classifiers/)

| Group | Count | Example |
|---|---|---|
| Development Status | 7 | `Development Status :: 5 - Production/Stable` |
| Environment | 74 | `Environment :: Web Environment` |
| Framework | 185 | `Framework :: Django :: 5.1` |
| Intended Audience | 14 | `Intended Audience :: Developers` |
| License | 84 | `License :: OSI Approved :: MIT License` |
| Natural Language | 64 | `Natural Language :: English` |
| Operating System | 43 | `Operating System :: OS Independent` |
| Programming Language | 102 | `Programming Language :: Python :: 3.12` |
| Topic | 320 | `Topic :: Software Development :: Libraries` |
| Typing | 2 | `Typing :: Typed` |

`pythonVersions` is extracted from `Programming Language :: Python :: 3.N` classifiers (3.0 … 3.16 exist today); `requiresPython` is the authoritative constraint.

#### pypistats categories

- `downloadsBySystem30d` keys: `Linux`, `Windows`, `Darwin`, `null` (unknown installer/CI), occasionally `other`.
- `downloadsByPythonMinor30d` keys: `2.7`, `3.6` … `3.14`, `null`.
- `downloadsHistory` categories: `without_mirrors` (default) or `with_mirrors` (`includeMirrors: true`).

#### License normalization (`licenseSource`)

`expression` (PEP 639 `license_expression`, SPDX) → `field` (short `license` string ≤ 60 chars) → `classifier` (`License :: …` leaf) → `null`. Projects that paste the whole license text into the field fall back to the classifier; the raw leaves are in `licenseClassifiers`.

### Examples

**Dependency due diligence with security and adoption data**

```json
{ "packages": ["requests", "fastapi", "langchain"], "downloadsBreakdown": true, "downloadsHistoryDays": 30 }
```

**Audit the exact versions in your lockfile**

```json
{ "packages": ["pillow==9.0.0", "urllib3==1.26.5", "cryptography==41.0.0"], "includeDownloads": false, "includeReleases": false }
```

**Lean weekly competitor tracker (small items, cheap to diff)**

```json
{ "packages": ["openai", "anthropic", "google-genai", "mistralai"], "fields": ["version", "downloadsWeek", "downloadsMonth", "lastRelease", "releasesLast90d", "vulnerabilityCount"] }
```

**Watch for new MCP / agent projects launching on PyPI (run every 15 min)**

```json
{ "mode": "newPackages", "keywords": ["mcp", "agent", "llm"] }
```

**Catch new releases in the LangChain ecosystem**

```json
{ "mode": "recentUpdates", "keywords": ["langchain", "langgraph"], "maxItems": 50 }
```

### Output

One item per package (trimmed):

```json
{
  "id": "fastapi",
  "name": "fastapi",
  "version": "0.141.1",
  "requestedVersion": null,
  "isLatest": true,
  "latestVersion": "0.141.1",
  "summary": "FastAPI framework, high performance, easy to learn, fast to code, ready for production",
  "license": "MIT",
  "licenseSource": "expression",
  "licenseExpression": "MIT",
  "licenseClassifiers": [],
  "developmentStatus": "Beta",
  "developmentStatusCode": 4,
  "requiresPython": ">=3.10",
  "pythonVersions": ["3.10", "3.11", "3.12", "3.13", "3.14"],
  "requires": [{ "name": "starlette", "normalized": "starlette", "specifier": ">=0.46.0", "extras": [], "marker": null, "extra": null, "url": null }],
  "requiresCount": 5,
  "requiresOptionalCount": 27,
  "extras": ["standard", "standard-no-fastapi-cloud-cli", "all"],
  "author": "Sebastián Ramírez",
  "authorEmail": "tiangolo@gmail.com",
  "owners": [{ "user": "tiangolo", "role": "Owner" }],
  "organization": null,
  "homepage": "https://github.com/fastapi/fastapi",
  "repository": "https://github.com/fastapi/fastapi",
  "documentation": "https://fastapi.tiangolo.com/",
  "changelog": "https://fastapi.tiangolo.com/release-notes/",
  "issues": "https://github.com/fastapi/fastapi/issues",
  "downloadsDay": null, "downloadsWeek": null, "downloadsMonth": null,
  "downloadsByPythonMinor30d": null,
  "downloadsBySystem30d": null,
  "downloadsError": "pypistats unavailable: 429 Too Many Requests after 4 attempts: https://pypistats.org/api/packages/fastapi/recent",
  "vulnerabilities": [],
  "vulnerabilityCount": 0,
  "versionsCount": 317, "firstRelease": "2018-12-08T08:14:13.103167Z", "lastRelease": "2026-07-29T17:18:04.364385Z",
  "daysSinceLastRelease": 45, "releasesLast90d": 24, "releasesLast365d": 92, "yankedVersionsCount": 0,
  "latestFiles": { "count": 2, "wheels": 1, "sdist": true, "totalSize": 557753, "publishedAt": "2026-07-29T17:18:04.364385Z" },
  "hasWheel": true, "yanked": false, "yankedReason": null,
  "recentVersions": [{ "version": "0.141.1", "publishedAt": "2026-07-29T17:18:04.364385Z", "files": 2, "hasWheel": true, "hasSdist": true, "yanked": false, "requiresPython": ">=3.10", "totalSize": 557753 }],
  "pypiUrl": "https://pypi.org/project/fastapi/",
  "sourceUrl": "https://pypi.org/pypi/fastapi/json",
  "fetchedAt": "2026-09-12T23:29:53.340Z"
}
```

This sample was captured while pypistats was rate-limiting our IP, so it shows the degraded shape. With pypistats available the download fields are integers and `{category: count}` maps, e.g. for `requests` (captured 2026-09-12): `downloadsHistory.series` entries like `{ "date": "2026-09-11", "downloads": 42125636 }`, `downloadsBySystem30d` like `{ "Linux": …, "Darwin": …, "Windows": …, "null": … }`, `downloadsByPythonMinor30d` like `{ "3.12": …, "3.11": …, "2.7": … }`.

| Field | Meaning |
|---|---|
| `id` | PEP 503 normalized name — stable key for joins/dedupe |
| `version`, `requestedVersion`, `latestVersion`, `isLatest` | Returned version; pinned version if any; newest release on PyPI; whether they match |
| `license`, `licenseSource`, `licenseExpression`, `licenseClassifiers` | See License normalization |
| `developmentStatus`, `developmentStatusCode` | From the Development Status classifier (null if not declared) |
| `requiresPython`, `pythonVersions` | Declared constraint; minor versions advertised via classifiers |
| `requires[]`, `requiresCount`, `requiresOptionalCount`, `extras` | Parsed PEP 508 requirements; count of mandatory vs. extra-only deps |
| `author`, `authorEmail`, `maintainer`, `maintainerEmail`, `owners[]`, `organization` | Metadata contacts (`Name <email>` lists are split into names / e-mails) and PyPI account owners/maintainers |
| `homepage`, `repository`, `documentation`, `changelog`, `issues`, `funding`, `projectUrls` | Normalized links (synonyms mapped, GitHub inferred) + raw map |
| `downloadsDay/Week/Month`, `downloadsByPythonMinor30d`, `downloadsBySystem30d`, `downloadsHistory`, `downloadsError` | pypistats data; `downloadsError` explains a null (new package, rate limit) |
| `vulnerabilities[]`, `vulnerabilityCount` | OSV advisories for the returned version: `id`, `aliases`, `summary`, `details`, `fixedIn`, `link`, `withdrawn` |
| `versionsCount`, `firstRelease`, `lastRelease`, `daysSinceLastRelease`, `releasesLast90d`, `releasesLast365d`, `yankedVersionsCount` | Release cadence |
| `latestFiles`, `hasWheel`, `yanked`, `yankedReason` | Artifacts of the returned version |
| `recentVersions[]` | Release history entries |
| `description`, `descriptionContentType` | Only when `descriptionChars > 0` |
| `pypiUrl`, `sourceUrl`, `fetchedAt` | Provenance |

Feed items (`recentUpdates` / `newPackages`): `id` (`name@version` or `name`), `feed`, `name`, `version`, `summary`, `author`, `publishedAt`, `pypiUrl`, `sourceUrl`, `fetchedAt`.

### Use it from code / agents

```bash
curl -X POST "https://api.apify.com/v2/acts/yadroo~pypi-package-intel/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"packages":["requests","fastapi"],"fields":["version","license","downloadsMonth","vulnerabilityCount"]}'
```

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('yadroo/pypi-package-intel').call({ packages: ['langchain', 'llama-index'], downloadsBreakdown: true });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```python
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("yadroo/pypi-package-intel").call(run_input={"packages": ["pillow==9.0.0"], "includeDownloads": False})
items = client.dataset(run["defaultDatasetId"]).list_items().items
```

MCP: add `https://mcp.apify.com` to your agent (Claude, Cursor, etc.) and call the `yadroo/pypi-package-intel` tool with the same JSON input.

### Pricing

Pay-per-event: **$0.001 per run start + $0.002 per item** (package or feed item). A due-diligence run on 10 packages costs about $0.021; a `newPackages` poll returning 40 items about $0.081. Packages/versions that do not exist or could not be fetched are not items and are not charged; batch names into one run to amortize the start fee.

### Limits & FAQ

- **Rate limits.** PyPI's JSON API is CDN-backed and generous. pypistats.org is IP rate-limited; the actor paces calls (~1.2 s apart) and backs off on 429 (up to ~1 min). If pypistats is rate-limited despite retries, the actor stops calling it for the rest of the run and every item carries `downloadsError` explaining why — metadata never depends on it. Counts are the same for 24 h, so re-run later for the numbers. Keep `downloadsBreakdown`/`downloadsHistoryDays` off for large batches.
- **Freshness.** Metadata is live. Downloads are computed once a day by pypistats (BigQuery) with ~1–2 days lag; do not poll download numbers more than once a day. Feeds are near-real-time but only expose the latest ~40 events — poll every 5–15 min to avoid gaps in busy niches.
- **Vulnerabilities** are what PyPI reports (OSV / PyPA advisory database) for the *returned* version; pin versions to audit older releases. An empty array means "none known", not "audited safe".
- **Unknown package or version** → no item (not charged); it is listed in the `SUMMARY` record of the default key-value store (`notFound[]`, and `errors[{input, error}]` for failed lookups, with counts) and in the run's status message. The run continues with the remaining names and fails only when no package could be fetched.
- **Not supported:** search by keyword across PyPI (no public API), per-version download counts (pypistats provides them but only for the last 180 days and per request — on the roadmap), reverse dependencies (needs libraries.io key or deps.dev — roadmap), downloading/inspecting wheels.
- **Roadmap:** per-version downloads, deps.dev dependents count, requirements.txt / pyproject upload as input.

***

Made by Yadroo. Sibling actors: [npm-package-intel](https://apify.com/yadroo/npm-package-intel) · [github-repo-intel](https://apify.com/yadroo/github-repo-intel) · [hackernews-search](https://apify.com/yadroo/hackernews-search) · [stackexchange-search](https://apify.com/yadroo/stackexchange-search)

# Actor input Schema

## `mode` (type: `string`):

`packages` returns one rich item per name in `packages`. `recentUpdates` / `newPackages` read PyPI's public RSS feeds (the last ~40 events, no history) — use them with `keywords` for monitoring new releases/projects in a niche (e.g. `mcp`, `langchain`, `agent`).

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

PyPI project names (case/separator-insensitive, PEP 503 normalized). Pin a specific version with `name==1.2.3` (or `name@1.2.3`) to get that release's metadata and the vulnerabilities known for exactly that version. Duplicates are skipped. Required in `packages` mode.

## `keywords` (type: `array`):

Only for `recentUpdates` / `newPackages`: keep feed items whose name or summary contains any of these words (case-insensitive). Empty = keep everything.

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

Cap for feed modes. Note the PyPI feeds themselves only carry the latest ~40 entries.

## `includeDownloads` (type: `boolean`):

Daily / weekly / monthly download counts (mirrors excluded). pypistats is IP rate-limited and refreshed once per day; the actor paces requests (~1 s each). Turn off for fastest metadata-only runs.

## `downloadsBreakdown` (type: `boolean`):

Adds `downloadsByPythonMinor30d` (e.g. {"3.12": 123456, ...}) and `downloadsBySystem30d` ({"Linux": ..., "Windows": ..., "Darwin": ..., "null": ...}). Costs 2 extra pypistats requests per package.

## `downloadsHistoryDays` (type: `integer`):

0 = off. N = add `downloadsHistory` with a daily series for the last N days (max 180 — that is all pypistats keeps) plus the total. 1 extra pypistats request per package.

## `includeMirrors` (type: `boolean`):

Include downloads by known mirrors (bandersnatch etc.) in the breakdown/history. The headline day/week/month numbers from pypistats always exclude mirrors.

## `includeVulnerabilities` (type: `boolean`):

Known vulnerabilities for the returned version, as reported by PyPI (source: OSV / PyPA advisory DB): id, aliases (CVE/GHSA), summary, fixed-in versions, link. `vulnerabilityCount` is always present.

## `includeReleases` (type: `boolean`):

Adds `recentVersions` (newest first): version, date, file count, wheel/sdist presence, yanked flag, requires\_python. Cadence metrics (`releasesLast90d`, `daysSinceLastRelease`, ...) are always computed.

## `versionsLimit` (type: `integer`):

How many entries to keep in `recentVersions`.

## `includeClassifiers` (type: `boolean`):

Full `classifiers` array. `developmentStatus`, `pythonVersions` and `licenseClassifiers` are extracted from them regardless.

## `descriptionChars` (type: `integer`):

0 = omit. N = include the first N characters of the project's long description (README) as `description`, with `descriptionContentType` (text/markdown, text/x-rst...).

## `fields` (type: `array`):

Optional whitelist of top-level output fields (e.g. \["version", "license", "downloadsMonth", "vulnerabilityCount"]). `id`, `name` and `error` are always kept. Empty = full item.

## Actor input object example

```json
{
  "mode": "packages",
  "packages": [
    "requests",
    "fastapi",
    "langchain"
  ],
  "keywords": [],
  "maxItems": 100,
  "includeDownloads": true,
  "downloadsBreakdown": false,
  "downloadsHistoryDays": 0,
  "includeMirrors": false,
  "includeVulnerabilities": true,
  "includeReleases": true,
  "versionsLimit": 10,
  "includeClassifiers": true,
  "descriptionChars": 0,
  "fields": []
}
```

# Actor output Schema

## `results` (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 = {
    "packages": [
        "requests",
        "fastapi",
        "langchain"
    ],
    "keywords": [],
    "fields": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("yadroo/pypi-package-intel").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": [
        "requests",
        "fastapi",
        "langchain",
    ],
    "keywords": [],
    "fields": [],
}

# Run the Actor and wait for it to finish
run = client.actor("yadroo/pypi-package-intel").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": [
    "requests",
    "fastapi",
    "langchain"
  ],
  "keywords": [],
  "fields": []
}' |
apify call yadroo/pypi-package-intel --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,yadroo/pypi-package-intel"
        }
    }
}
```

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/VutlqDAeAayTtlIXK/builds/Ou68fJ8JpeFMByKqf/openapi.json
