npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

pypi-api-client

v1.0.0

Published

TypeScript client for the PyPI REST API

Readme

pypi-api-client

CI npm version npm downloads/week npm downloads/month Bundle size License: MIT TypeScript Node.js semantic-release

TypeScript client for the Python Package Index. Aggregates data from multiple sources into a single, chainable API — project metadata, all published versions, distribution files, download statistics, vulnerability reports, and resolved dependency graphs. Works in Node.js and the browser (isomorphic). Fully typed, zero runtime dependencies.

Data sources integrated:

| Source | What it provides | | --- | --- | | pypi.org | Project metadata, all versions, distribution files, classifiers, vulnerability reports | | pypistats.org | Download counts by period, Python version, and operating system | | api.deps.dev/v3 | Fully resolved dependency graph with exact versions, direct and transitive classification |


Installation

npm install pypi-api-client

Quick start

import { PyPIClient } from 'pypi-api-client';

// Public APIs — no auth required
const pip = new PyPIClient();

// Custom base URLs (e.g. mirrors, proxies)
const custom = new PyPIClient({
  apiUrl:      'https://my-pypi-mirror.example.com',
  statsApiUrl: 'https://my-stats-proxy.example.com',
  depsDevUrl:  'https://api.deps.dev/v3',
});

API reference

Project metadata

// Full project — all versions, classifiers, dependencies, vulnerability reports
const project = await pip.package('requests');
const project = await pip.package('requests').get(); // same

// Info block only (latest version metadata)
const info = await pip.package('requests').info();
console.log(info.name);             // 'requests'
console.log(info.version);          // '2.31.0'
console.log(info.requires_python);  // '>=3.7'
console.log(info.license);          // 'Apache 2.0'
console.log(info.classifiers);      // ['Programming Language :: Python :: 3', ...]
console.log(info.requires_dist);    // ['urllib3>=1.21.1,<3', 'certifi>=2017.4.17', ...]
console.log(info.project_urls);     // { Homepage: '...', Source: '...', Documentation: '...' }

// All version strings (order matches PyPI insertion order)
const versions = await pip.package('requests').versions();
console.log(versions); // ['2.0.0', '2.1.0', ..., '2.31.0']

// Full releases map — version → distribution files
const releases = await pip.package('requests').releases();
console.log(Object.keys(releases).length); // number of published versions

Version metadata

// Specific version
const info = await pip.package('requests').version('2.31.0');
const info = await pip.package('requests').version('2.31.0').get(); // same

// Latest published version
const latest = await pip.package('requests').latest();
const latest = await pip.package('requests').latest().get(); // same

console.log(info.info.version);          // '2.31.0'
console.log(info.info.requires_python);  // '>=3.7'
console.log(info.last_serial);           // PyPI event serial

Distribution files

// Files included in a specific version (wheels, sdists)
const files = await pip.package('requests').version('2.31.0').files();

files.forEach(f => {
  console.log(f.filename);              // 'requests-2.31.0-py3-none-any.whl'
  console.log(f.packagetype);           // 'bdist_wheel' | 'sdist'
  console.log(f.size);                  // file size in bytes
  console.log(f.python_version);        // 'py3' | 'cp311' | 'source'
  console.log(f.requires_python);       // '>=3.7'
  console.log(f.digests.sha256);        // SHA-256 integrity hash
  console.log(f.upload_time_iso_8601);  // '2023-05-22T15:00:00Z'
  console.log(f.yanked);                // false
});

// Filter to wheels only
const wheels = files.filter(f => f.packagetype === 'bdist_wheel');

// Filter to source distributions
const sdists = files.filter(f => f.packagetype === 'sdist');

// Latest version files
const latestFiles = await pip.package('requests').latest().files();

Vulnerabilities

// Known vulnerabilities for the latest version
const vulns = await pip.package('requests').vulnerabilities();

// Known vulnerabilities for a specific version
const vulns = await pip.package('requests').version('2.28.0').vulnerabilities();

vulns.forEach(v => {
  console.log(v.id);        // 'GHSA-j8r2-6x86-q33q'
  console.log(v.aliases);   // ['CVE-2023-32681']
  console.log(v.summary);   // 'Unintended leak of Proxy-Authorization header'
  console.log(v.details);   // full description
  console.log(v.fixed_in);  // ['2.31.0']
  console.log(v.link);      // advisory URL
  console.log(v.source);    // 'osv'
});

// Check if a version is affected
if (vulns.length > 0) {
  const fixedIn = vulns.flatMap(v => v.fixed_in);
  console.log('Upgrade to:', fixedIn);
}

Download statistics

// Last day, week, and month totals in a single request
const stats = await pip.package('requests').downloads();

console.log(stats.data.last_day);    // 1234
console.log(stats.data.last_week);   // 8638
console.log(stats.data.last_month);  // 35274

// Downloads by Python major version (2 vs 3)
const byMajor = await pip.package('requests').downloadsByPythonMajor();
byMajor.data.forEach(row => {
  console.log(`Python ${row.category}: ${row.downloads} on ${row.date}`);
});

// Downloads by Python minor version (3.8, 3.9, 3.10, 3.11, …)
const byMinor = await pip.package('requests').downloadsByPythonMinor();
byMinor.data.forEach(row => {
  console.log(`Python ${row.category}: ${row.downloads} on ${row.date}`);
});

// Downloads by operating system
const byOS = await pip.package('requests').downloadsBySystem();
byOS.data.forEach(row => {
  console.log(`${row.category}: ${row.downloads} on ${row.date}`);
  // 'Linux' | 'Windows' | 'Darwin' | 'null'
});

// Downloads with and without mirror traffic
const byMirrors = await pip.package('requests').downloadsByMirrors();
byMirrors.data.forEach(row => {
  console.log(`${row.category}: ${row.downloads} on ${row.date}`);
  // 'with_mirrors' | 'without_mirrors'
});

// All breakdown endpoints accept optional date filters
const filtered = await pip.package('requests').downloadsByPythonMinor({
  start_date: '2024-01-01',
  end_date:   '2024-01-31',
});

Resolved dependency graph — deps.dev

Returns exact resolved versions for every dependency in the tree — not the semver ranges from requires_dist, but what actually installs.

const deps = await pip.package('requests').version('2.31.0').dependencies();

// All nodes in the graph
deps.nodes.forEach(n => {
  console.log(`${n.relation}: ${n.versionKey.name}@${n.versionKey.version}`);
  // 'SELF':     [email protected]
  // 'DIRECT':   [email protected]
  // 'DIRECT':   [email protected]
  // 'INDIRECT': ...
});

// Direct dependencies only
const direct = deps.nodes.filter(n => n.relation === 'DIRECT');

// Transitive dependencies only
const transitive = deps.nodes.filter(n => n.relation === 'INDIRECT');

// Bundled dependencies (embedded in the distribution file)
const bundled = deps.nodes.filter(n => n.bundled);

// Resolution errors (broken or unresolvable versions)
const broken = deps.nodes.filter(n => n.errors.length > 0);

// Graph edges — who requires whom and with what constraint
deps.edges.forEach(e => {
  const from = deps.nodes[e.fromNode].versionKey.name;
  const to   = deps.nodes[e.toNode].versionKey.name;
  console.log(`${from} → ${to} (${e.requirement})`);
});

// Latest version dependency graph (auto-resolves current version)
const latestDeps = await pip.package('requests').latest().dependencies();

Chainable resource pattern

Package resources implement PromiseLike, so you can await them directly or chain methods:

// Await directly → fetches the full project
const project = await pip.package('requests');

// Chain → fetches a specific version
const v = await pip.package('requests').version('2.31.0');

// Chain → latest version
const latest = await pip.package('requests').latest();

Cancelling requests

Pass an AbortSignal to any method to cancel the in-flight request:

const controller = new AbortController();
setTimeout(() => controller.abort(), 3000);

await pip.package('requests').get(controller.signal);
await pip.package('requests').info(controller.signal);
await pip.package('requests').versions(controller.signal);
await pip.package('requests').vulnerabilities(controller.signal);
await pip.package('requests').downloads(controller.signal);
await pip.package('requests').downloadsByPythonMinor({}, controller.signal);
await pip.package('requests').version('2.31.0').get(controller.signal);
await pip.package('requests').version('2.31.0').files(controller.signal);
await pip.package('requests').version('2.31.0').dependencies(controller.signal);

When aborted, fetch throws a DOMException with name === 'AbortError'. The request event is still emitted with the error attached.


Request events

Subscribe to every HTTP request for logging, monitoring, or debugging:

pip.on('request', (event) => {
  console.log(`[${event.method}] ${event.url} → ${event.statusCode} (${event.durationMs}ms)`);
  if (event.error) {
    console.error('Request failed:', event.error.message);
  }
});

| Field | Type | Description | | --- | --- | --- | | url | string | Full URL that was requested | | method | 'GET' | HTTP method used | | startedAt | Date | When the request started | | finishedAt | Date | When the request finished | | durationMs | number | Duration in milliseconds | | statusCode | number \| undefined | HTTP status code, if a response was received | | error | Error \| undefined | Present only if the request failed |

Multiple listeners can be registered. The event is always emitted after the request completes, whether it succeeded or failed. Events are emitted for all data sources — PyPI API, pypistats.org, and deps.dev.

The on() method is chainable:

pip
  .on('request', logHandler)
  .on('request', metricsHandler);

Error handling

Non-2xx responses throw a PyPIApiError with the HTTP status code and status text:

import { PyPIApiError } from 'pypi-api-client';

try {
  await pip.package('nonexistent-xyz').get();
} catch (err) {
  if (err instanceof PyPIApiError) {
    console.log(err.status);     // 404
    console.log(err.statusText); // 'Not Found'
    console.log(err.message);    // 'PyPI API error: 404 Not Found'
  }
}

TypeScript types

All domain types are exported:

import type {
  // Client
  PyPIClientOptions, RequestEvent, PyPIClientEvents,

  // Project
  PyPIProject, PyPIProjectInfo, PyPIFile, PyPIVulnerability,

  // Version
  PyPIVersionInfo,

  // Downloads
  PyPIRecentDownloads, PyPIRecentDownloadsData,
  PyPIBreakdownDownloads, PyPIDownloadRow, PyPIDownloadParams,

  // deps.dev
  PyPIDepsDevDependencies, PyPIDepsDevNode, PyPIDepsDevEdge, PyPIDepsDevVersionKey,
} from 'pypi-api-client';

Documentation

Full API documentation is published at: https://eljijuna.github.io/pypi-api-client


Contributing

See CONTRIBUTING.md.


License

MIT