# Software End-of-Life Monitor: Support Dates & Calendar (`felixgoldwin/software-support-deadline-monitor`) Actor

Track software end-of-life, active support and extended support dates from endoflife.date. Compare saved release-cycle snapshots, detect approaching deadlines, and export an iCalendar file with source and vendor links.

- **URL**: https://apify.com/felixgoldwin/software-support-deadline-monitor.md
- **Developed by:** [Felix GoldWin](https://apify.com/felixgoldwin) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 delivered release cycles

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

## Software Support Deadline Monitor

Turn a software portfolio into a release-cycle support watchlist. Get separate
active-support, end-of-life, and extended-support dates; exact before/after
changes; approaching-deadline transitions; and an importable calendar. Every
record links back to its public source.

Built for platform teams, IT service providers, and software portfolio owners
who need to review support deadlines regularly. The data comes from the public
[endoflife.date v1 API](https://endoflife.date/docs/api/v1/). The paid value is
bounded collection, consistent exports, explicit comparisons, and Apify workflow
integration. endoflife.date itself is freely accessible.

The upstream v1 API is **Beta** and can change. This Actor validates the expected
schema and fails clearly on incompatible responses; it does not promise API stability.

### Quick start

Use this input in Apify's input editor:

```json
{
  "watchlist": ["nodejs:22", "python:3.13", "postgresql:17"],
  "maxItems": 100,
  "maxRequests": 20,
  "deadlineWindowDays": 90
}
```

Use `nodejs:22` for the exact cycle named `22`; use `nodejs` for every documented
Node.js cycle. Find product slugs and cycle names on
[endoflife.date](https://endoflife.date/). A version such as `22.14.0` is not
automatically mapped to a cycle. No approximate version matching occurs.

On the first run, records have `changeType: "baseline"`. To compare a later run,
download the first run's default key-value store `SNAPSHOT` JSON and paste its
entire object into `previousSnapshot` while keeping the same watchlist. A
snapshot from another Actor or watchlist is rejected. Runs do not discover or
overwrite another run's snapshot automatically. A recurring Apify workflow must
explicitly pass the prior snapshot into each run.

### What you receive

Each dataset row represents one canonical product and release cycle. Download
the dataset as JSON, CSV, or Excel from Apify. Nested changes and deadline data
are most useful in JSON.

| Field | Meaning |
| --- | --- |
| `id` | Stable canonical `product:cycle` identity, such as `nodejs:22` |
| `product`, `productLabel`, `productCategory` | Source product identity |
| `releaseCycle`, `releaseLabel`, `codename`, `releaseDate` | Exact release-cycle details |
| `activeSupportEnded`, `activeSupportEndDate` | Source active-support status and end date |
| `eol`, `eolDate` | Source end-of-life status and date |
| `extendedSupportEnded`, `extendedSupportEndDate` | Extended support; null when unknown or not applicable |
| `maintained` | Whether any source-listed support remains, including commercial support |
| `isLts`, `ltsFrom` | Source long-term-support status and start date |
| `latestVersion`, `latestReleaseDate`, `releaseNotesUrl` | Latest documented version within the cycle |
| `supportLabels` | Product-specific meanings of the active, EOL, and extended phases |
| `custom` | Sorted source-specific string/null fields, when present |
| `deadlines` | Known phase deadlines as `{kind,date}`; unknown dates are omitted |
| `deadlineStatus` | Each deadline's UTC day difference and urgency bucket |
| `changeType`, `changes` | Baseline, added, changed, or unchanged, with exact field-level before/after values |
| `thresholdCrossings` | A known unchanged deadline moving between urgency buckets since the prior snapshot |
| `sourceUrl`, `sourceApiUrl`, `releasePolicyUrl` | Source page, API endpoint, and vendor policy link |
| `sourceGeneratedAt`, `sourceLastModifiedAt`, `sourceSchemaVersion` | Source provenance, distinct from this run's observation date |

The default key-value store also contains:

- `OUTPUT`: completion status, request counts, comparison counts, limits, and diagnostics.
- `SNAPSHOT`: a bounded baseline suitable for `previousSnapshot` on a later run.
- `DEADLINES.ics`: an all-day calendar of the known deadlines in delivered records.

Calendar dates use the published date without inventing a time zone or time of
day. Calendar applications differ in how they handle repeated imports; use the
stable event identifiers when building an integration.

### Interpret support correctly

An EOL release can still have `maintained: true` if the source lists extended or
commercial support. This does not mean the software remains covered by free
upstream security updates or that your organization owns a support contract.
Read `supportLabels` and the vendor policy before making an upgrade decision.

Booleans come from the source's generation time. `asOfDate` controls countdowns
and threshold calculations; it does not retrieve a historical upstream response
or recompute the source's booleans. Date changes are reported in `changes`;
threshold transitions concern the same deadline date across observations.

The Actor preserves nulls instead of inventing support dates. It never infers
vulnerabilities, installed versions, license entitlement, or upgrade
compatibility. It collects no product-description prose, personal contact data,
or credentials.

### Inputs and bounds

| Input | Default | Bounds and behavior |
| --- | --- | --- |
| `watchlist` | Node.js 22, Python 3.13, PostgreSQL 17 | 1–50 entries, at most 25 distinct products; duplicates removed; an entire-product entry overrides its cycle-specific entries |
| `maxItems` | 100 | 1–5,000 delivered release-cycle records |
| `maxRequests` | 20 | 1–200 HTTP attempts, including retries and redirects |
| `requestIntervalMs` | 1,000 | 500–60,000 ms between requests; sequential collection |
| `requestTimeoutSecs` | 30 | 5–60 seconds per request |
| `maxRetries` | 2 | 0–3 retries for transient failures |
| `deadlineWindowDays` | 90 | 1–730 days; governs urgency buckets, not source filtering |
| `asOfDate` | Current UTC date | Real `YYYY-MM-DD` date for deterministic countdowns |
| `previousSnapshot` | None | Matching Actor/watchlist snapshot, at most 2 MiB and 5,000 records |

Only `https://endoflife.date/api/v1/products/{slug}/` is requested. One product
request supplies every selected cycle for that product. Vendor policies and
release notes are returned as links and are never fetched. No bulk `/full` dump,
login, proxy rotation, CAPTCHA bypass, or access-restriction workaround is used.
Responses are limited to 4 MiB; only up to three same-host redirects are followed.
The HTTP layer applies bounded retries and respects `Retry-After` within its
retry policy. Source responses can add new fields without breaking the Actor;
invalid documented field types fail explicitly.

`sourceScanComplete: false` signals truncation, an exhausted request or billing
budget, missing requested cycles, or conflicting duplicate records. Never treat
an incomplete scan as evidence that a cycle disappeared. With two complete
snapshots, `notObservedIds` only means previously seen IDs were absent from this
query; it does not prove a vendor removed a release. Fix unknown cycles before
using a snapshot as a complete portfolio baseline.

### Local development and verification

Requirements: Node.js 24 or later and npm. From this Actor directory:

```sh
npm ci
npm test
mkdir -p storage/key_value_stores/default
cp examples/input.json storage/key_value_stores/default/INPUT.json
npm start
```

The start command makes bounded public network requests and writes local Apify
storage. It does not publish the Actor. Do not provide an Apify token for local
fixture tests. Docker builds use the included Dockerfile and lockfile.

`test/fixtures/nodejs.live.json` was downloaded from the public API on
2026-10-11. Source tests run offline and cover validation, exact selection,
canonical deduplication, unknown phases, bounds, upstream schema failures, and
request-budget exhaustion. Modified test variants are synthetic. A live smoke
check verifies reachability and current response compatibility; it is distinct
from the fixture tests and does not prove every upstream product remains valid.

See `examples/input.json` and `examples/output.json` for a concrete input and
sample dataset row. The output example is a deterministic rendering of the
checked-in fixture, dated 2026-10-11, and is not a current-data guarantee.

### Upload and pricing

Create a new Actor in your own Apify account, upload this source folder or push
it using the Apify CLI, build it, and run the example with a small limit. Inspect
the dataset, `OUTPUT`, `SNAPSHOT`, and calendar before publishing to the Store.
Keep `NOTICE` with the source and retain endoflife.date attribution in the
listing. No account creation, upload, Store publication, or paid Apify run was
performed to prepare this source package.

Approved launch pricing is **$0.005 per automatic start billing unit plus $0.005 per delivered cycle** ($5 per 1,000 cycle records, plus the start fee). Configure `apify-actor-start` and `apify-default-dataset-item` in Apify Console. Apify charges startup automatically; never add a manual `Actor.charge()` call for it or a duplicate custom start/result event.

Up to and including 1 GB RAM, startup is one unit; higher memory multiplies the count (4 GB means four units). These Actors default to 256 MB and cap memory at 512 MB, so startup is $0.005 within the supplied configuration. At 256 MB, 100 delivered cycles cost $0.505 in event charges; a started zero-result or subsequently failed run can still incur $0.005. Other applicable costs follow Apify's terms.

Each successfully delivered record counts once, including unchanged records. Errors, missing cycles, summaries, snapshots and calendar files create no extra dataset-item charge. The SDK includes automatic startup in its already-consumed budget; the existing runtime checks remaining item capacity before fetching and each delivery. If none remains, it fetches and delivers nothing. `OUTPUT.billing` describes this item-cap check, not the complete Apify bill.

This pricing is an approved launch choice, not proof of demand or profit. See [PUBLISHING.md](PUBLISHING.md) and [examples/pricing.json](examples/pricing.json) for event settings, arithmetic examples, and budget behavior.

### Source and reuse

The API's [OpenAPI specification](https://endoflife.date/docs/api/v1/openapi.yml)
declares the [MIT License](https://github.com/endoflife-date/endoflife.date/blob/master/LICENSE).
The public [robots.txt](https://endoflife.date/robots.txt) allowed all paths when
checked on 2026-10-11. The specification documents HTTP 429 and `Retry-After`
but does not state a numeric request quota. Defaults are deliberately modest.
Preserve the included upstream notice when redistributing the fixture or
substantial source data. Product logos and Wikipedia-derived descriptions are
not included. This Actor is independent of endoflife.date and the software vendors.

# Actor input Schema

## `watchlist` (type: `array`):

Exact endoflife.date slugs. Use nodejs:22 to monitor one cycle or nodejs to monitor every cycle. Maximum 50 entries and 25 distinct products. Product names are case insensitive; cycle names must match exactly. All-cycle entries override narrower entries for the same product.

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

Hard cap on unique delivered and billable records. Runtime also respects the Apify spending cap.

## `maxRequests` (type: `integer`):

Total source HTTP attempts, including retries and redirects.

## `requestIntervalMs` (type: `integer`):

Sequential requests only; minimum 500 ms between attempts.

## `requestTimeoutSecs` (type: `integer`):

Timeout for each response including its body.

## `maxRetries` (type: `integer`):

Retries only transient network failures, 429, 500, 502, 503 and 504; all count toward maxRequests.

## `deadlineWindowDays` (type: `integer`):

Classify dated deadlines into UTC calendar-day buckets. This does not filter records.

## `asOfDate` (type: `string`):

Optional YYYY-MM-DD reference for deadline date arithmetic; defaults to UTC today. This does not retrieve historical source state.

## `previousSnapshot` (type: `object`):

Optional JSON object copied from this Actor’s SNAPSHOT output. Max 2 MiB and 5000 records, same filters. Never paste secrets. Each run uses only the snapshot explicitly supplied.

## Actor input object example

```json
{
  "watchlist": [
    "nodejs:22",
    "python:3.13",
    "postgresql:17"
  ],
  "maxItems": 100,
  "maxRequests": 20,
  "requestIntervalMs": 1000,
  "requestTimeoutSecs": 30,
  "maxRetries": 2,
  "deadlineWindowDays": 90
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `summary` (type: `string`):

No description

## `snapshot` (type: `string`):

No description

## `calendar` (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 = {
    "watchlist": [
        "nodejs:22",
        "python:3.13",
        "postgresql:17"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("felixgoldwin/software-support-deadline-monitor").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 = { "watchlist": [
        "nodejs:22",
        "python:3.13",
        "postgresql:17",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("felixgoldwin/software-support-deadline-monitor").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 '{
  "watchlist": [
    "nodejs:22",
    "python:3.13",
    "postgresql:17"
  ]
}' |
apify call felixgoldwin/software-support-deadline-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,felixgoldwin/software-support-deadline-monitor"
        }
    }
}
```

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/vkLCyBnfxzFTSuirZ/builds/nvAlIK06R7ZBTM9bp/openapi.json
