# Package Health Check — Maintenance Status Across 6 Ecosystems (`m_ctim/package-health-check`) Actor

Check whether an open-source package is actively maintained, stale, or abandoned — npm, PyPI, crates.io, RubyGems, Packagist, or NuGet, plus linked GitHub repo signals, in one report per package.

- **URL**: https://apify.com/m\_ctim/package-health-check.md
- **Developed by:** [Timothy Kelvin](https://apify.com/m_ctim) (community)
- **Categories:** Developer tools, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## 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 Health Check: Maintenance Status Across 6 Ecosystems

Give it a package name and its ecosystem. Get back its latest version,
license, linked GitHub repo signals (stars, open issues, last push), and a
computed maintenance status: `ACTIVE`, `STALE`, or `ABANDONED`. Works
across npm, PyPI, crates.io, RubyGems, Packagist, and NuGet with one
consistent output shape, so you're not juggling six different registry
formats to answer "is this thing safe to depend on."

### Who this is for

- **Engineering leads and AppSec teams** doing dependency review before adopting a new package, across any of the six ecosystems.
- **Due-diligence and vendor-risk reviewers** checking whether a vendor's stated tech stack relies on abandoned packages.
- **Open-source maintainers** auditing their own dependency tree for things that need replacing.

This is a maintenance-health check, not a vulnerability scanner: it tells
you whether a package is still being actively worked on, not whether it has
known CVEs.

### Input

| Field | Type | Description |
|---|---|---|
| `packages` | array | `[{ "ecosystem": "npm", "name": "express" }]`. Ecosystem must be one of `npm`, `pypi`, `crates`, `rubygems`, `packagist`, `nuget`. |
| `githubToken` | string (optional) | A personal access token to raise the GitHub API limit from 60 to 5,000 requests/hour when checking many packages with linked GitHub repos in one run. |

```json
{
  "packages": [
    { "ecosystem": "npm", "name": "express" },
    { "ecosystem": "pypi", "name": "requests" },
    { "ecosystem": "crates", "name": "serde" }
  ]
}
```

### Output

One record per package:

```json
{
  "ecosystem": "npm",
  "packageName": "express",
  "found": true,
  "latestVersion": "4.21.2",
  "publishedAt": "2025-12-01T10:00:00.000Z",
  "license": "MIT",
  "homepageUrl": "http://expressjs.com/",
  "repositoryUrl": "https://github.com/expressjs/express",
  "deprecatedOrYanked": false,
  "github": {
    "stars": 66000,
    "openIssues": 120,
    "pushedAt": "2026-08-01T12:00:00.000Z",
    "archived": false
  },
  "lastActivityAt": "2026-08-01T12:00:00.000Z",
  "maintenanceStatus": "ACTIVE",
  "checkedAt": "2026-09-19T12:00:00.000Z"
}
```

`maintenanceStatus` is computed from the more recent of the registry's last
publish date and the linked GitHub repo's last push date:

- **ACTIVE**: activity within the last 180 days
- **STALE**: last activity 180–730 days ago
- **ABANDONED**: over 730 days, or the package is deprecated/yanked, or its repo is archived
- **UNKNOWN**: no usable date found (rare; usually means no linked repo and the registry didn't report a publish date)

If a package isn't found in the registry, the record is `{ "found": false }`
and no GitHub lookup is attempted.

### How it works

Direct calls to each registry's own public API (npmjs.org, pypi.org,
crates.io, rubygems.org, packagist.org, nuget.org): no scraping, no proxy.
When a registry entry has a `repositoryUrl` pointing to GitHub, one
additional call to the public GitHub REST API adds stars/issues/last-push
signal.

**GitHub rate limit note:** unauthenticated GitHub API access is capped at
60 requests/hour per source IP: enough for small-to-medium batches. For
large batches, pass your own `githubToken` (a GitHub personal access token
with no special scopes needed) to raise that to 5,000/hour. Packages
without a detected GitHub repo URL skip this step entirely and aren't
affected by the limit.

### Related products

- [NPM Package Update Tracker](https://github.com/timmKal01/npm-package-tracker): version-by-version release history for npm, instead of a single current health snapshot
- [RubyGems Package Lookup](https://github.com/timmKal01/rubygems-package-lookup) / [NuGet Package Lookup](https://github.com/timmKal01/nuget-package-lookup) / [Packagist Package Lookup](https://github.com/timmKal01/packagist-package-lookup): full metadata for a single ecosystem, if you don't need the cross-ecosystem health rollup

# Actor input Schema

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

List of packages to check: \[{ "ecosystem": "npm", "name": "left-pad" }]. Supported ecosystems: npm, pypi, crates, rubygems, packagist, nuget.

## `githubToken` (type: `string`):

Optional personal access token to raise the GitHub API rate limit from 60 to 5,000 requests/hour when checking linked repos in bulk. Leave blank for small batches.

## Actor input object example

```json
{
  "packages": [
    {
      "ecosystem": "npm",
      "name": "express"
    }
  ]
}
```

# 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": [
        {
            "ecosystem": "npm",
            "name": "express"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("m_ctim/package-health-check").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": [{
            "ecosystem": "npm",
            "name": "express",
        }] }

# Run the Actor and wait for it to finish
run = client.actor("m_ctim/package-health-check").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": [
    {
      "ecosystem": "npm",
      "name": "express"
    }
  ]
}' |
apify call m_ctim/package-health-check --silent --output-dataset

```

## MCP server setup

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

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/knCWE0M6FyW6iqF5E/builds/9qqQj8sjvDmFeuMJh/openapi.json
