# Software Support & End-of-Life Lookup for AI Agents (`grit-77/software-support-lookup`) Actor

Software support and end-of-life lookup for explicit product/release pairs from endoflife.date. Export flat JSON lifecycle records and days to end of life for upgrade planning.

- **URL**: https://apify.com/grit-77/software-support-lookup.md
- **Developed by:** [Grit](https://apify.com/grit-77) (community)
- **Categories:** Developer tools, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.20 / 1,000 support lookups

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 & End-of-Life Lookup for AI Agents

Fetch support-lifecycle records for explicit software product/release pairs and give upgrade-planning agents a flat JSON result with days to end of life.

The data source is the public [endoflife.date API v1](https://endoflife.date/docs/api/v1/). The Actor requests only /products/{product}/releases/{release}; it does not discover products or enumerate releases. This is software support-lifecycle data. PyPI/npm metadata and NVD vulnerability data are outside its scope. Product descriptions, logos, contributor fields and raw source objects are excluded from result rows.

### What it returns

| Field | Type | Meaning |
|---|---|---|
| product | string/null | Requested endoflife.date slug, trimmed and lowercased; null for an invalid nonstring input |
| release | string/null | Exact requested release identifier as a string |
| releaseDate | date string/null | Release-cycle start date |
| latestVersion | string/null | Latest version within this release cycle, not the newest major release across the product |
| latestDate | date string/null | Date of that latest version |
| eoasFrom | date string/boolean/null | End of active support; missing/unknown becomes null; any source boolean is preserved |
| eolFrom | date string/boolean/null | End of life; missing/unknown becomes null; any source boolean is preserved |
| isEoas | boolean/null | Source flag for end of active support; null when absent |
| isEol | boolean/null | Source end-of-life flag |
| isMaintained | boolean/null | Source maintenance flag; may include extended support |
| sourceGeneratedAt | timestamp string/null | API envelope generated_at, preserved exactly; not the Actor's retrieval time |
| asOf | date string | Explicit planning date supplied by the caller |
| daysToEol | integer/null | eolFrom minus asOf in calendar days; 0 on that date, negative afterwards, null when eolFrom is not a date |
| sourceUrl | string/null | Exact official API endpoint queried; null for an invalid pair |
| error | object/null | Null on success; otherwise code, message and httpStatus |

Date strings use YYYY-MM-DD. API v1 normally supplies lifecycle dates as dates or null; the projection also preserves boolean values without inventing a date. false, true and null remain distinct JSON values. Source flags describe the API snapshot: changing asOf changes the day calculation, not those flags. This Actor cannot reconstruct historical support status.

### Input example

The Store prefill is:

```json
{
  "products": [{"product": "python", "release": "3.13"}],
  "asOf": "2026-10-06",
  "maxItems": 1
}
```

Both products and asOf are required. Use the source's exact product slug and release-cycle name, for example python and "3.13"; a version number must be a JSON string. Product slugs are lowercased and surrounding whitespace is removed. Releases are not guessed from installed patch versions, and aliases such as "latest" are not resolved to another release.

Optional controls: maxItems defaults to 1000 (range 1-10000) when omitted; the prefill sets it to 1. concurrency defaults to 5 (range 1-20). The input list can contain at most 100000 pairs. Only the first maxItems pairs are processed. Duplicates are processed as supplied; success and error rows both count toward the cap. Rows are saved in input order.

### Output example

One actual local result from 2026-10-06 is shown below. Source values can change on later runs.

```json
{
  "product": "python",
  "release": "3.13",
  "releaseDate": "2024-10-07",
  "latestVersion": "3.13.16",
  "latestDate": "2026-09-30",
  "eoasFrom": "2026-10-01",
  "eolFrom": "2029-10-31",
  "isEoas": true,
  "isEol": false,
  "isMaintained": true,
  "sourceGeneratedAt": "2026-10-06T07:10:13+00:00",
  "sourceUrl": "https://endoflife.date/api/v1/products/python/releases/3.13",
  "asOf": "2026-10-06",
  "daysToEol": 1121,
  "error": null
}
```

The full one-row sample is in [sample_output.json](sample_output.json). Results are written to the default dataset; the Actor output links to its JSON items endpoint.

### Pricing

Proposed event: **support_lookup — $0.0002 per successful stored row**, or $0.20 per 1000 successful rows. Error rows do not trigger this event. There is no proposed custom startup event. A reached event charge limit stops further output; rows already fetched in a concurrent batch can be omitted.

On the captured 2026-10-06 Store comparison, the recent-usage leader among returned direct comparables was endoflife.date Python Snapshot at $0.002 per cycle. This proposal is below that primary event and the lowest primary result price returned ($0.0004). See [PRICING.md](PRICING.md) for names, dated API evidence, ranking rules and limitations. Monetization remains to be configured when the owner publishes.

### Limits and failures

- Only explicit, public endoflife.date API v1 lookups; no login or source API key is supplied.
- Invalid pairs and unknown products/releases yield error rows. Other valid pairs continue. Invalid top-level input, an empty product list or an invalid planning date fails the run with a clear message.
- Transport failures, HTTP 429 and 5xx receive at most three total attempts, using exponential waits of 0.5 then 1 second. A numeric Retry-After can extend a wait to at most 30 seconds. HTTP timeouts are set to 20 seconds.
- HTTP 404 yields not_found; other unsuccessful status codes yield source_http; exhausted transport errors yield source_unavailable; malformed JSON or unusable lifecycle fields yield invalid_source.
- Source coverage and update timing govern the results. Null is not evidence that a release is supported. Source dates alone do not describe support-contract eligibility, security fixes or licensing.

### Use with AI agents / MCP

Use this Actor as a lifecycle lookup tool in an upgrade-planning workflow. Supply an explicit planning date and a small maxItems value. Fetch the returned dataset and check error before reading lifecycle fields. Inspect sourceGeneratedAt for freshness, distinguish null from false, and use daysToEol to sort dated deadlines. A latestVersion result is a patch version within the requested release, not an upgrade recommendation.

The same input object can be passed through an Apify MCP integration after publication; see [Apify’s official AI-agent integration guide](https://docs.apify.com/get-started/agent-onboarding). Tool wiring and cloud execution are not configured or tested by this local build. No agent-generated description or guessed lifecycle date is added to the output.

### FAQ

**Why does a maintained release have an expired active-support date?** The API can report continued security or extended support after active support ends. isMaintained and isEoas are separate source flags.

**Why is daysToEol null?** The source did not provide a calendar EOL date. The Actor preserves null or a source boolean rather than treating it as a date.

**Can I pass Python 3.13.16 as the release?** Use the exact release identifier provided by endoflife.date (3.13 in this example). Patch versions and aliases are not mapped to release cycles.

**Can I backdate asOf to retrieve historical data?** asOf only controls the calendar-day difference. The API still returns its current generated snapshot.

**Do failed lookups incur the custom event?** No. Only successfully normalized and saved rows trigger support_lookup; errors still count toward maxItems.

### Local development

Create storage/key_value_stores/default/INPUT.json using the input example. From the Actor directory, run in PowerShell:

```powershell
$env:APIFY_LOCAL_STORAGE_DIR = "$PWD/storage"
$env:APIFY_IS_AT_HOME = "0"
& C:/GritWork/earn/.venv/Scripts/python -B -m src
& C:/GritWork/earn/.venv/Scripts/python -B -m pytest tests -q -p no:cacheprovider
apify validate-schema
```

The full test command includes the marked live test and needs internet access. Local storage retains earlier dataset rows if reused; use a fresh storage directory for an isolated repeat run. The local SDK warned that the event was ignored because this local Actor had no pay-per-event configuration; no Store billing was exercised.

Validation receipts: [final tests](evidence/tests-final.log), [required schema command](evidence/schema-validation.log), [output/metadata checks](evidence/metadata-validation.log), and [source-blind actor proof](evidence/behavior-proof.json). Final test output: **46 passed in 1.22s**. The prefilled local run saved one result; its lookup-and-storage loop took **0.55s** ([run log](evidence/local-run.log)). These are measurements of this local run, not a throughput guarantee. Docker and cloud execution remain unverified.

# Actor input Schema

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

Use endoflife.date product slugs and exact release names as strings. Each pair is queried independently; no product discovery or release enumeration. Invalid individual pairs produce error rows.

## `asOf` (type: `string`):

Required calendar date for daysToEol. Source support flags remain the API snapshot and are not recomputed for this date.

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

Process only the first maxItems supplied pairs. Successful and error rows both count toward this cap.

## `concurrency` (type: `integer`):

Maximum simultaneous API requests. Rows are saved in input order. Already fetched rows may be omitted if the charge limit is reached.

## Actor input object example

```json
{
  "products": [
    {
      "product": "python",
      "release": "3.13"
    }
  ],
  "asOf": "2026-10-06",
  "maxItems": 1,
  "concurrency": 5
}
```

# Actor output Schema

## `dataset` (type: `string`):

Software support lookup rows, source timestamps and per-pair errors.

# 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": [
        {
            "product": "python",
            "release": "3.13"
        }
    ],
    "asOf": "2026-10-06",
    "maxItems": 1
};

// Run the Actor and wait for it to finish
const run = await client.actor("grit-77/software-support-lookup").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": [{
            "product": "python",
            "release": "3.13",
        }],
    "asOf": "2026-10-06",
    "maxItems": 1,
}

# Run the Actor and wait for it to finish
run = client.actor("grit-77/software-support-lookup").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": [
    {
      "product": "python",
      "release": "3.13"
    }
  ],
  "asOf": "2026-10-06",
  "maxItems": 1
}' |
apify call grit-77/software-support-lookup --silent --output-dataset

```

## MCP server setup

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

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/fvdbCfXPF2KL4a6dP/builds/ehJyYAehrcZS2rcWf/openapi.json
