# Software End of Life Date, Support Dates: Batch EOL Lookup (`autoaugeo/software-eol-batch-lookup-b787`) Actor

Batch EOL lookup: end of life date and support dates for many software products per run. Data from https://endoflife.date (MIT): informational, may be out of date, check vendor. Not affiliated with or endorsed by endoflife.date or any vendor. No API key, pay per found result, source URL per record.

- **URL**: https://apify.com/autoaugeo/software-eol-batch-lookup-b787.md
- **Developed by:** [Augeo auto](https://apify.com/autoaugeo) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 results

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 End of Life (EOL Date) Batch Lookup

Batch EOL lookup for software end of life: the end of life date and support dates of many products in one run, read from endoflife.date (<https://endoflife.date>, MIT licence). The records are aggregated third-party data, not produced by this Actor or its operator, and every record carries its source URL. Dates are informational, may be out of date and should be checked against the vendor. The Actor is not affiliated with or endorsed by endoflife.date, Microsoft or any vendor.

The input is `products`, an array of many product names in one run, each optionally followed by a version (`python 3.8`, `ubuntu 22.04`, `nodejs`), and the output is one JSON record per matched release cycle with the fields `query`, `product`, `cycle`, `release_date`, `eol_date` (the EOL date), `support_end_date` (the support end date), `is_eol`, `days_until_eol`, `lts`, `latest_version`, `source_url`, `retrieved_at` and `status`. Compared with the other end-of-life Actors we found in the Apify Store, it takes one batch array of plain product-and-version strings and resolves a full version such as `3.8.10` to its release cycle, needs no API key and no other credentials, charges per found result with not-found records free, and puts the source URL on every record.

It is built for agents and scripts that hold an inventory such as `python 3.8`, `ubuntu 22.04`, `nodejs 22.11.0` and need a verdict per item in a single run, without knowing the exact product slug or release cycle name.

- No login, no API key, no proxy.
- Up to 200 lookups per run.
- You pay one `result` event per record with status `found`. Records with status `not_found`, `version_not_found` or `source_error` are returned free.
- A run with an empty input uses the default: it looks up `python 3.8`, `ubuntu 22.04` and `nodejs 22.11.0`.

### Data source and licence

Data source: endoflife.date (https://endoflife.date), MIT licence, Copyright 2020 endoflife.date contributors. The Actor reads its public API v1 (`https://endoflife.date/api/v1`) and nothing else.

- The records are aggregated third-party data. This Actor and its operator do not produce or correct lifecycle dates; the Actor looks them up and reshapes them. Every record carries the `source_url` of the page the data comes from.
- Dates are informational, may be out of date, and should be checked against the vendor before you act on them. No accuracy is guaranteed, and no accuracy rate has been measured for this Actor.
- This Actor is not affiliated with or endorsed by endoflife.date, Microsoft or any vendor. Product names are used descriptively only, to say what is looked up. The Actor uses and returns no logos or product icons.

The copyright and permission notice of the source, which ships with this Actor as its licence file, is reproduced in full below:

> Copyright 2020 endoflife.date contributors
>
> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
>
> The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
>
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

### Use cases for a batch EOL lookup

Each job below is one run. The input has two fields, `products` and `only_supported`, and nothing else. Product names in the examples are resolved against the product list of endoflife.date at run time, like any other string; a name or version the source does not have comes back as a free record, not as a failed run.

**1. Dependency audit of a stack list.** Pass the runtimes, frameworks and databases of a service as one array and read `is_eol`, `eol_date` and `days_until_eol` per item.

```json
{
    "products": ["python 3.8", "django 4.2", "postgresql 13", "redis 7.2", "mysql 8.0"]
}
```

**2. CI gate on end of life runtimes.** A pipeline step starts a run with the versions it builds on, reads the dataset and fails the pipeline in its own code when a record has `is_eol` true or a `days_until_eol` under its threshold. The Actor does not fail a run because an item is end of life. Keep `only_supported` off here: with it on, an unmaintained cycle produces no record.

```json
{
    "products": ["nodejs 18", "python 3.8"],
    "only_supported": false
}
```

**3. Asset inventory enrichment.** Send the operating systems of an inventory and join the records back on `query`, which repeats the input string. Add `eol_date`, `support_end_date`, `lts` and `source_url` to each asset row.

```json
{
    "products": ["ubuntu 22.04", "debian 11", "amazon linux 2023"]
}
```

**4. Upgrade planning.** A string with no version returns every release cycle of the product; with `only_supported` on, only the cycles the source reports as maintained. Each returned cycle is one charged result.

```json
{
    "products": ["nodejs", "postgresql"],
    "only_supported": true
}
```

**5. Tool for an agent.** An agent that holds product and version strings as it found them makes one call and gets a `status` it can branch on and a `source_url` it can cite. A full version or a codename is matched to its release cycle, so the agent does not need the cycle names of the source.

```json
{
    "products": ["python 3.8.10", "nodejs 22.11.0", "ubuntu jammy"]
}
```

What it does not do: it keeps nothing between runs, compares nothing with an earlier run and sends no alerts.

### Input

| Field | Type | Required | Description |
|---|---|---|---|
| `products` | array of strings | no (default `["python 3.8", "ubuntu 22.04", "nodejs 22.11.0"]`) | One string per lookup: a product name, optionally followed by a version. 1 to 200 items. When the field is left out, the run uses the default. |
| `only_supported` | boolean | no (default `false`) | Leave out release cycles the source reports as no longer maintained. Left-out cycles are not charged. Not-found records are still returned. |

Example:

```json
{
    "products": ["python 3.8", "ubuntu 22.04", "nodejs 22.11.0"],
    "only_supported": false
}
```

#### How an item is read

1. **Product.** The Actor downloads the product list of the source once per run and matches the longest leading part of your string against product names, aliases and labels, ignoring case, spaces and punctuation. `Amazon Linux 2023`, `amazon-linux 2023` and `amazon-linux@2023` all resolve to product `amazon-linux` with version `2023`.
2. **Version.** What is left after the product is the version. A leading `v` and a trailing `LTS` are ignored. The version is matched to a release cycle by the first rule that applies:
   1. the cycle name equals the version (`3.8`);
   2. the cycle label or codename equals the version (`ubuntu jammy`);
   3. the longest cycle name that starts the version (`3.8.10` gives cycle `3.8`, `22.11.0` gives cycle `22`);
   4. the version is the leading part of several cycle names (`python 3` gives every `3.x` cycle, one record each).
3. **No version.** `nodejs` alone returns every release cycle of the product, one record each. Each of those records is one charged result.

Repeated identical strings in one run are answered once.

### Output

One dataset record per matched release cycle. Every record has the same 13 keys, in this order:

| Key | Type | Description |
|---|---|---|
| `query` | string | The input string this record answers. |
| `product` | string or null | Product name as used by the source, for example `python`. Null when status is `not_found`, or when the product list could not be read. |
| `cycle` | string or null | Release cycle, for example `3.8` or `22.04`. |
| `release_date` | string or null | Date the cycle was first released, `YYYY-MM-DD`. |
| `eol_date` | string or null | Date the cycle reaches end of life, `YYYY-MM-DD`. Null when the source does not know it. |
| `support_end_date` | string or null | Date active support ends, `YYYY-MM-DD`. Null when the product has no active support phase or the date is unknown. |
| `is_eol` | boolean or null | True when the source reports the cycle as end of life. |
| `days_until_eol` | integer or null | Whole days from the run date (UTC) to `eol_date`. Negative when the date has passed. |
| `lts` | boolean or null | True when the source marks the cycle as a long-term support release. |
| `latest_version` | string or null | Latest version published in the cycle. |
| `source_url` | string | Page on endoflife.date the data comes from. For `not_found` it is the product list that was searched. |
| `retrieved_at` | string | UTC time the data was fetched from the source, ISO 8601. |
| `status` | string | `found`, `not_found`, `version_not_found` or `source_error`. See the table below. |

| Status | Meaning | Charged |
|---|---|---|
| `found` | A release cycle matched. One record per cycle. | yes |
| `not_found` | The source does not list the product, or answered HTTP 404 for it. | no |
| `version_not_found` | The product is listed but has no release cycle for that version. | no |
| `source_error` | The source did not give a usable answer for the product (HTTP 429 or 5xx after 3 retries, a network error, or an answer with no release cycles). Run the string again later. | no |

The cycles are whatever the source returns for the product at run time: the Actor holds no list of products or cycle names of its own.

#### Example record (illustrative)

The record below is an illustrative example written for this README. It is not the output of a run: it shows the 13 keys of the Actor's dataset schema, in order, with values in the right format. Real values come from the source at run time.

```json
{
    "query": "python 3.8",
    "product": "python",
    "cycle": "3.8",
    "release_date": "2019-10-14",
    "eol_date": "2024-10-07",
    "support_end_date": "2021-05-03",
    "is_eol": true,
    "days_until_eol": -727,
    "lts": false,
    "latest_version": "3.8.20",
    "source_url": "https://endoflife.date/python",
    "retrieved_at": "2026-10-04T22:30:00Z",
    "status": "found"
}
```

A record that is not charged has the same 13 keys, with null for the cycle data. Also an illustrative example, not the output of a run:

```json
{
    "query": "python 9.9",
    "product": "python",
    "cycle": null,
    "release_date": null,
    "eol_date": null,
    "support_end_date": null,
    "is_eol": null,
    "days_until_eol": null,
    "lts": null,
    "latest_version": null,
    "source_url": "https://endoflife.date/python",
    "retrieved_at": "2026-10-04T22:30:00Z",
    "status": "version_not_found"
}
```

### What you pay for

The Store prices the `result` event at USD 0.002. The pricing panel of the Store describes that event as "One item in the output dataset". That text overstates the charge: the Actor raises the event only for items with status `found`, which are the lifecycle records. This is what the code does for each record:

- status `found`: one `result` event is charged, then the record is written to the dataset;
- status `not_found`, `version_not_found` or `source_error`: the record is written to the dataset and no event is charged.

So a run that finds nothing still fills the dataset, one row per input string, and costs nothing in events.

### Price

Pay per event. The Actor has one event, named `result`: **$0.002 per `result`** (so $2 per 1,000).

Charged: exactly one `result` event for each record pushed to the dataset with status `found`.

Not charged:

- records with status `not_found`, `version_not_found` or `source_error` (they are pushed to the dataset, with no event);
- release cycles left out by `only_supported`;
- repeated identical strings in one run (answered once);
- starting the run: there is no start fee.

The default input (`python 3.8`, `ubuntu 22.04`, `nodejs 22.11.0`) asks for one release cycle per string, so it gives at most 3 `result` events.

#### Maximum charge per run

If you set a maximum charge for the run, the Actor respects it. Each `found` record is charged before it is pushed, so a record the maximum cannot pay for is never pushed. When the maximum is reached the Actor stops, pushes nothing further (free records included) and the run ends as succeeded. The status message then says how many input strings were processed and how many were skipped, for example `Stopped at the maximum charge set for this run: 40 of 200 products processed, 160 skipped.` A string counts as processed when all its records were pushed. Run the skipped strings again in a new run.

A string with no version returns every cycle of that product and each cycle is one result. Add the version, or turn on `only_supported`, to get fewer records.

### Running it on a schedule

The Actor can be started from an Apify schedule with a saved input, like any Actor. Each run reads the source again and writes a full set of records to its own dataset, each with its `retrieved_at` time. This turns the software end of life check into a recurring one.

- The Actor keeps no state between runs. It does not compare a run with the previous one and it sends no alerts.
- To detect a change, compare `eol_date`, `support_end_date`, `is_eol` and `latest_version` between two datasets, matching records on `product` and `cycle`, in your own code.
- Every scheduled run charges its `found` records again. A weekly run of 50 strings that each match one cycle is at most 50 `result` events a week, $0.10.
- Set a maximum charge on the scheduled runs if the list may grow.

### How it treats the source

- One request for the product list, then one request per distinct product in the run, whatever the number of versions asked for that product. 200 items across 20 products cost 21 requests.
- At most 2 requests in flight, with a pause of 0.25 seconds before each request.
- A descriptive User-Agent that names the Actor.
- On HTTP 429 or 5xx it waits (the `Retry-After` value when given, otherwise 2, 4 and 8 seconds) and retries at most 3 times per URL. HTTP 404 is not retried.
- It reads only the JSON API. It does not scrape HTML pages and does not use product icons or descriptions.

### Limits

- At most 200 items in `products` per run. A longer list is refused before any lookup and nothing is charged; split it across runs.
- If the source still fails after the retries for a product, every string for that product gets one free record with status `source_error` and the rest of the batch is still answered. If the product list itself cannot be read, every string of the run gets a `source_error` record.
- Run result: a run with some `source_error` records next to answered ones ends as succeeded (exit code 0), so check `status` on each record. The run ends as failed (exit code 1) only when no string was answered by the source, that is when every record is a `source_error`; nothing is charged in that case. The final status message gives the counts, for example `2 cycles found, 1 queries not found, 1 queries with a source error`. An input that cannot be read (an item that is not a string, only blank strings, more than 200 items) is refused before any request and also ends as failed, with nothing charged.
- The API of the source is in beta and its fields can change. Fields the Actor does not know are ignored, and a missing or unreadable field is returned as null. End of life, active support and LTS are read both as a date and as a true/false value, whichever the source gives.
- `is_eol` is the value reported by the source. `days_until_eol` is computed by the Actor from `eol_date` and the run date.
- Product matching uses the names, aliases and labels of the source. A product the source does not track gets status `not_found`.
- With `only_supported` on, a string whose every matched cycle is unmaintained produces no record at all.

### FAQ: end of life date and support dates

**Where does the data come from?**
From endoflife.date (<https://endoflife.date>), used under the MIT licence. The Actor reads its public API and no other source. The records are aggregated third-party data: this Actor and its operator do not produce or correct the dates. Every record carries the `source_url` of the page it comes from. The Actor is not affiliated with or endorsed by endoflife.date, Microsoft or any vendor.

**How fresh is it?**
Each run fetches from the source at run time and keeps no copy between runs; `retrieved_at` on every record is the UTC time of that fetch. How current a date is depends on the source. Dates are informational, may be out of date, and should be checked against the vendor before you act on them. No accuracy rate has been measured for this Actor.

**What happens with a product name the source does not know?**
The string gets one free record with status `not_found`, and the rest of the batch is still answered. A known product with a version that matches no release cycle gets a free `version_not_found` record. Matching ignores case, spaces and punctuation and uses the names, aliases and labels of the source; see "How an item is read".

**What are the limits per run?**
`products` takes 1 to 200 items. A longer list is refused before any lookup and nothing is charged; split it across runs. Left out, `products` defaults to `python 3.8`, `ubuntu 22.04` and `nodejs 22.11.0`. `only_supported` is a boolean and defaults to `false`. There are no other input fields.

**Do I need an API key or an account at the source?**
No. The Actor reads the public API of endoflife.date with no login, no key and no proxy.

**What does a run cost?**
$0.002 for each record with status `found`, and nothing else: no start fee. The default input gives at most 3 `result` events, $0.006. A run of 200 strings that each match one release cycle gives at most 200 events, $0.40. A string with no version costs one event per release cycle of that product.

**Can I cap what a run costs?**
Yes. Set a maximum charge for the run. The Actor stops when it is reached and the status message says how many strings were processed and how many were skipped.

**Does it tell me when a date changes?**
No. It returns the data as it is at run time. See "Running it on a schedule".

# Actor input Schema

## `products` (type: `array`):

One string per lookup: a product name, optionally followed by a version. Examples: python 3.8, ubuntu 22.04, nodejs 22.11.0, amazon linux 2023, nodejs. A full version is matched to its release cycle (3.8.10 gives cycle 3.8). With no version the Actor returns every release cycle of the product, and each cycle is one charged result. At most 200 items per run; a longer list is refused. When the field is left out, the run uses the default: python 3.8, ubuntu 22.04, nodejs 22.11.0.

## `only_supported` (type: `boolean`):

When on, release cycles that the source reports as no longer maintained are left out of the results and are not charged. Not-found records are still returned.

## Actor input object example

```json
{
  "products": [
    "python 3.8",
    "ubuntu 22.04",
    "nodejs 22.11.0"
  ],
  "only_supported": false
}
```

# 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 = {
    "products": [
        "python 3.8",
        "ubuntu 22.04",
        "nodejs 22.11.0"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("autoaugeo/software-eol-batch-lookup-b787").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 = { "products": [
        "python 3.8",
        "ubuntu 22.04",
        "nodejs 22.11.0",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("autoaugeo/software-eol-batch-lookup-b787").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 '{
  "products": [
    "python 3.8",
    "ubuntu 22.04",
    "nodejs 22.11.0"
  ]
}' |
apify call autoaugeo/software-eol-batch-lookup-b787 --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,autoaugeo/software-eol-batch-lookup-b787"
        }
    }
}
```

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/LPcY0uSXPc9xmCOrw/builds/uM1sTX4f7dHPTaEgH/openapi.json
