# Changelog to JSON - Release Notes, GitHub Releases, npm & PyPI (`neverempty/changelog-to-json`) Actor

For AI agents and developers upgrading a dependency: give a changelog page, GitHub repo or npm/PyPI package, get one JSON row per version with date and added / fixed / removed / breaking items, only versions newer than yours if you ask. On 41 real changelogs, 371 of 375 versions returned were real.

- **URL**: https://apify.com/neverempty/changelog-to-json.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Developer tools, AI, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.80 / 1,000 version returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Changelog to JSON - Release Notes, GitHub Releases, npm & PyPI

**For AI agents and developers who need to know what changed in a library or API since the version they use.** Give a changelog page, a GitHub repository or an npm / PyPI package; get back **one JSON row per version**: version number, date, the notes sorted into `added` / `changed` / `deprecated` / `removed` / `fixed` / `security` / `breaking` lists, whether a breaking-change marker is written, and the full notes text. Ask for `sinceVersion` to get only the versions newer than yours, or turn on monitor mode to get only versions that appeared since the last run.

### Quick example

Input:

```json
{
  "repos": ["expressjs/express"],
  "maxVersions": 2
}
```

First output row (real run, shortened):

```json
{
  "status": "ok",
  "input": "expressjs/express",
  "repository": "expressjs/express",
  "source": "changelog-file",
  "sourceUrl": "https://github.com/expressjs/express/blob/HEAD/History.md",
  "version": "5.2.1",
  "tag": "v5.2.1",
  "title": "5.2.1 / 2025-12-01",
  "date": "2025-12-01",
  "dateSource": "heading",
  "releasePublishedAt": "2025-12-01T20:54:44Z",
  "isPrerelease": false,
  "hasBreakingMarker": false,
  "breakingMarkers": [],
  "fixed": [],
  "otherChanges": ["Revert security fix for [CVE-2024-51999](https://www.cve.org…"],
  "itemCount": 1,
  "entryUrl": "https://github.com/expressjs/express/releases/tag/v5.2.1",
  "alsoFoundIn": "github-releases",
  "versionsFound": 304,
  "note": "Of 304 version(s) found: 11 pre-release(s) left out (includePrereleases); 291 older one(s) beyond maxVersions 2."
}
```

The version was found both in the repository's History.md and in GitHub Releases; the row uses the History.md text (`source`) and adds the release page and publish time from GitHub. The notes of 5.2.1 mention "an erroneous breaking change" of the previous release, which is **not** a breaking-change marker for this version (only a label such as `BREAKING:` or a "Breaking changes" heading is).

To get only what changed since your version, add `"sinceVersion": "5.1.0"`. For an npm or PyPI package use `npmPackages` / `pypiPackages`; for any changelog page use `urls`.

With no input at all, the Actor reads the example `https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md` and returns its newest 3 versions.

### What it reads

| Input | How it is read |
|---|---|
| **Changelog page URL** (`urls`) | `CHANGELOG.md`, `HISTORY.md`, `CHANGES.rst`, `Releases.md` and similar files (a `github.com/.../blob/...` link is read as the raw file), `/changelog` and `/release-notes` web pages, and single release-notes pages. The page is split at the headings that carry a version number or a date; pages that list dated posts (a `<time>` element or a date line next to each title) are split at those dates. |
| **GitHub repository** (`repos`, `owner/name`) | Both the **GitHub Releases** list (official REST API) and the **changelog file** in the repository are read and matched by version. The `source` column says which one each row came from (`changelog-file` or `github-releases`) and `alsoFoundIn` says when the other one had the same version. For a package in a monorepo write `owner/name/path/to/package`. |
| **npm package** (`npmPackages`) | The repository is taken from the npm registry, then read like a GitHub repository (the changelog in the package's own folder first). `registryLatestVersion` is the latest published version. |
| **PyPI package** (`pypiPackages`) | PyPI's public release feed: the newest 40 version numbers with upload times. **No release notes** — PyPI's project pages answer automated readers with a check page and its JSON API is disallowed by its robots.txt, so this Actor does not use them. Give the repository or the changelog URL to get the notes. |

Formats handled (measured on real changelogs, see below): Keep a Changelog (`## [1.2.0] - 2024-01-02`, `## [Unreleased]`), release-please / standard-version (`## [1.2.0](compare-link) (2024-01-02)` with `### Features` / `### Bug Fixes` / `### ⚠ BREAKING CHANGES`), Changesets (`### Major Changes` / `### Minor Changes` / `### Patch Changes`), setext and reStructuredText headings (`Version 3.0.0` underlined, `Released 2023-09-30` on the next line), plain lines such as `v10.11.0 - September 18, 2026`, calendar versions (`2026.9.1`, not read as a date), date-style API versions (`2026-08-26.dahlia`), `go1.27.0 (released 2026-08-19)`, dates in English (`September 5, 2026`, `5 Sep 2026`), ISO and Japanese (`2026年9月24日`), and headings such as `Sep 22` under a `September, 2026` heading (the year is taken from the heading above and `dateSource` says so).

### Honest limits

- **A page that cannot be split is not guessed at.** If no heading carries a version or a date, the row says `no-versions-found` and why (free). Set `returnWholeTextIfUnsplit` to get the whole text as one row instead (charged as one row).
- **Breaking changes are marked only when the text says so**: a heading such as `### ⚠ BREAKING CHANGES` or `Backwards incompatible changes in 5.2`, a label at the start of an item (`BREAKING:`, `**Breaking:**`, `Breaking change:`, `破壊的変更:`), an upper-case `BREAKING`, or a `feat!:` style commit. A mid-sentence mention ("the previous release included an erroneous breaking change"), a negated form (`Non-breaking changes`), a `⚠️ Notable Changes` heading and a major version bump alone are **not** marked. `hasBreakingMarker: false` means "no marker is written", not "certainly compatible".
- **Pages drawn by JavaScript** (the text is not in the HTML) are reported as `js-required` (free). When an HTML page cannot be split, the Actor asks the site once more for its Markdown version (`Accept: text/markdown`), which some documentation sites serve to AI agents; `note` says when that version was used.
- **GitHub API allowance**: without a token GitHub allows about 60 API calls per hour per IP address. The Actor checks the remaining allowance first (that check is free), uses one call per 100 releases, and when the allowance is used up it reads only the changelog file and writes that in `note` (or returns a free `rate-limited` row if there is no changelog file). Add `githubToken` (no scopes needed) for 5,000 per hour; it is sent only to `api.github.com`.
- Pre-releases written after the version with a space (`Python 3.14.0 beta 4`, `3.14.0 release candidate 2`) are returned as `3.14.0-beta.4` / `3.14.0-rc.2` with `isPrerelease: true`, never as the final release.
- The same changelog given twice through different fields (for example its URL in `urls` and the package in `npmPackages`) is read and charged twice; the same field written differently is read once.
- Pages over 10 MB are read up to 10 MB (`note` says so).
- `date` is the date written in the changelog; for GitHub Releases it is the release's publish time. Registry upload times can differ from the written date by a day or two.

### Respecting sites

Every address is checked against the site's **robots.txt** before it is requested (RFC 9309, longest match, `*` and `$` patterns; this Actor's own name `NeverEmptyChangelog` is honoured if a site names it). A disallowed address is not requested and comes back as a free `robots-disallowed` row. The Actor does not sign in, does not solve or bypass check pages or CAPTCHAs, does not use proxies to get around a refusal, and does not read private or internal network addresses. It identifies itself with the user agent `NeverEmptyChangelog/1.0`.

### Measured accuracy

On 41 real changelogs (2026-09-25; 25 files and repositories checked against an independent source — the npm registry's publish times, PyPI's release feed or GitHub Releases — and 16 web pages checked by reading the page):

| Check | Result |
|---|---|
| Returned versions that really exist (newest 15 per changelog) | **371 / 375 (98.9%)**; the 4 others are written in the changelog but were never published to npm (for example `openai` 7.12.0) or are a changelog entry for a pre-release |
| Published versions in the covered range that were returned | **347 / 352 (98.6%)**; the 5 missing ones (React 19.2.8 and 19.1.9, axios 1.14.1 and 1.18.1, Prettier 3.9.2) have no heading in those changelogs (checked by searching the files) |
| Dates within 3 days of the registry / GitHub date | **292 / 299 (97.7%)** |
| Web pages whose newest version or post (title and date) was read correctly | **16 / 16** (Stripe API, Cloudflare, Claude API, OpenAI API, Resend, Linear, Vercel, Supabase, Notion, Go, Python, Kubernetes, Django 5.2, VS Code, kintone (Japanese), GitHub REST API) |

Covered: React, Vite, axios, Express, Anthropic SDK (TypeScript and Python), OpenAI SDK (Node and Python), Stripe Node, Tailwind CSS, Svelte, SvelteKit, Crawlee, Prettier, ESLint, Jest, Vue, Flask, Requests, Pydantic, FastAPI, HTTPX, Deno, Rust, Misskey (calendar versions) and the 16 pages above.

### Monitor mode (only new versions)

Set `onlyNewVersions: true` and a `watchName`. The first run returns the newest versions (up to `maxVersions`) with `changeType: "first-check"` and remembers them and the older versions below them. Later runs return only versions that were not returned or remembered before (`changeType: "new"`), newest first, up to `maxVersions` per run (the rest come in the next run). A run with no new version returns one free `no-new-versions` row and is charged only the run start fee.

- A version that was left out as **Unreleased** or as a **pre-release** is not remembered, so it comes back as new once it is released (for example Flask's `Version 3.2.0 / Unreleased` becoming `Released 2026-10-01`).
- A **maintenance release** of an older line (a new 4.22.3 below 5.2.1) is returned as new too.
- Entries without a version number (dated posts) are told apart by date, title and the start of their text, so two posts on the same day are two entries.
- If a source could not be read at earlier checks (for example GitHub Releases while the hourly allowance was used up), its versions are taken as the starting point the first time it is read and are **not** sold as new; `note` says how many.
- Versions not returned because the run hit its maximum charge are not remembered, so the next run returns them.
- If the platform restarts a run, rows already returned are not returned or charged again.

### Pricing

Pay per event:

- **Version returned** — each row with a version (or, when you ask for it, one unsplit changelog text).
- **Run start** — once per run that returned at least one version, or, in monitor mode, once per run that read a changelog and compared it (even when nothing is new).

Free: every row that says why nothing was returned (not found, robots.txt disallows, cannot be split, no newer version, rate limited, blocked, needs JavaScript, bad input). A run whose maximum total charge cannot cover the start fee plus one version requests nothing and is charged nothing.

### Input fields

| Field | Type | Default | Description |
|---|---|---|---|
| `urls` | list | – | Changelog files and pages. GitHub repository, npmjs.com and pypi.org package links are read as repositories / packages. |
| `repos` | list | – | GitHub repositories as `owner/name` (or `owner/name/path/to/package`). |
| `npmPackages` | list | – | npm package names. |
| `pypiPackages` | list | – | PyPI project names (versions and dates only). |
| `sinceVersion` | text | – | Only versions greater than this (semantic-version order; pre-releases sort before their release). |
| `sinceDate` | text | – | Only versions dated after this day (YYYY-MM-DD). |
| `maxVersions` | integer | 20 | Newest N versions per input (1–1,000). |
| `includePrereleases` | boolean | false | Also return alpha / beta / rc / canary / next / dev versions and GitHub pre-releases. |
| `includeUnreleased` | boolean | false | Also return an "Unreleased" section. |
| `includeBody` | boolean | true | Include the full notes text as Markdown (`body`, up to 20,000 characters). |
| `returnWholeTextIfUnsplit` | boolean | false | Return a page that cannot be split as one row of text. |
| `onlyNewVersions` | boolean | false | Monitor mode: only versions not seen in earlier runs of the same watch. |
| `watchName` | text | – | Name of the remembered state for monitor mode. |
| `resetMonitoringState` | boolean | false | Forget what this watch remembered. |
| `githubToken` | secret | – | Optional GitHub token for a higher API allowance; sent only to api.github.com. |
| `maxConcurrency` | integer | 4 | Inputs read at the same time (1–8). |
| `requestTimeoutSecs` | integer | 25 | Time limit per request (5–60 s). 429 and 5xx answers are asked again up to two more times. |

### Output columns

`status` (`ok`, `unsplit`, or the reason: `no-versions-found`, `no-versions-after`, `no-new-versions`, `no-changelog-found`, `rate-limited`, `js-required`, `robots-disallowed`, `robots-unreachable`, `login-required`, `blocked`, `not-found`, `http-error`, `unreachable`, `unreadable`, `bad-input`, `budget-reached`), `position`, `input`, `inputType`, `repository`, `package`, `source`, `sourceUrl`, `httpStatus`, `version`, `tag`, `title`, `date`, `dateSource`, `releasePublishedAt`, `isPrerelease`, `isUnreleased`, `hasBreakingMarker`, `breakingMarkers`, `added`, `changed`, `deprecated`, `removed`, `fixed`, `security`, `breaking`, `otherChanges`, `sections`, `itemCount`, `body`, `bodyTruncated`, `entryUrl`, `alsoFoundIn`, `registryLatestVersion`, `versionsFound`, `sortedBy`, `changeType`, `watchName`, `note`, `checkedAt`.

Rows are sorted newest first: by version number when at least 80% of the versions are numbers (so a maintenance release of an older line does not jump above the current line), otherwise by date, otherwise in page order (`sortedBy`).

### Support

Found a changelog that is split wrongly? Open an issue in the **Issues** tab with the URL and what you expected; it helps the next person.

# Actor input Schema

## `urls` (type: `array`):

Changelog or release-notes pages, one per line: a CHANGELOG.md / HISTORY.md / CHANGES.rst file (a github.com/.../blob/... link is read as the raw file), a /changelog or /release-notes web page, or a single release-notes page. A github.com/owner/name link is read as a repository (see Repositories); an npmjs.com or pypi.org package link is read as that package. If no input field is filled at all, the example https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md is used (newest 3 versions).

## `repos` (type: `array`):

GitHub repositories as owner/name (for example facebook/react), one per line. Both the GitHub Releases list (official API) and the changelog file in the repository (CHANGELOG.md, CHANGES.md, HISTORY.md, NEWS.md, RELEASES.md, CHANGELOG.rst and similar) are read and matched by version; the source column says where each row came from. For a package inside a monorepo write owner/name/path/to/package to look for the changelog file there first.

## `npmPackages` (type: `array`):

npm package names (for example react or @anthropic-ai/sdk). The package's repository is read from the npm registry and then handled like a GitHub repository (the changelog in the package's own folder first). The latest published version is added to every row as registryLatestVersion.

## `pypiPackages` (type: `array`):

PyPI project names (for example django). PyPI's public release feed gives the newest 40 version numbers and upload times only, without release notes; give the project's GitHub repository in Repositories or its changelog URL in Changelog page URLs to get the notes.

## `sinceVersion` (type: `string`):

Return only versions greater than this one (for example 1.4.0 or v2.3.1), compared as version numbers (1.10.0 is newer than 1.9.0; a pre-release such as 2.0.0-rc.1 is older than 2.0.0). Rows without a version number are left out when this is set. A date-style API version such as 2024-09-30.acacia is compared by its date.

## `sinceDate` (type: `string`):

Return only versions dated after this day (YYYY-MM-DD). Rows without a date are left out when this is set.

## `maxVersions` (type: `integer`):

At most this many versions per changelog, newest first (1 to 1,000). Each returned version is one charged row.

## `includePrereleases` (type: `boolean`):

Also return versions marked as pre-releases (alpha, beta, rc, canary, next, dev, nightly, or a GitHub release marked pre-release). Off by default.

## `includeUnreleased` (type: `boolean`):

Also return an "Unreleased" / "Upcoming" section at the top of a changelog (isUnreleased is true on that row). Off by default.

## `includeBody` (type: `boolean`):

Return the whole text of each version as Markdown in body (up to 20,000 characters). Turn off for smaller rows with only the sorted lists (added, fixed, breaking and the others).

## `returnWholeTextIfUnsplit` (type: `boolean`):

When a page has no heading with a version number or a date, return its whole text as one row (status unsplit, charged as one row) instead of a free row that says it could not be split.

## `onlyNewVersions` (type: `boolean`):

Return only versions that did not appear in the previous runs with the same watch name. The first run returns the newest versions (up to Max versions per input) and remembers every version on the page. A run with no new version returns a free row saying so and charges only the run start fee.

## `watchName` (type: `string`):

Name of the remembered state used to compare runs (letters, digits, dot, dash, underscore; up to 40). Setting it (or turning on monitor mode) fills changeType (first-check or new). Use a different name for each list you check on its own schedule. With monitor mode on and no name, the name "default" is used.

## `resetMonitoringState` (type: `boolean`):

Start this watch over: forget the remembered versions before this run.

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

Optional. Without a token, GitHub allows about 60 API calls per hour for the IP address the run uses; this Actor checks the remaining allowance first (the check itself is free) and, when it is used up, reads only the changelog files and says so. A personal access token with no scopes raises the allowance to 5,000 per hour. It is sent only to api.github.com.

## `maxConcurrency` (type: `integer`):

How many inputs are read in parallel (1 to 8).

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

How long to wait for one answer (5 to 60 seconds). HTTP 429, 500, 502, 503 and 504 are asked again up to two more times.

## Actor input object example

```json
{
  "urls": [
    "https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md"
  ],
  "maxVersions": 20,
  "includePrereleases": false,
  "includeUnreleased": false,
  "includeBody": true,
  "returnWholeTextIfUnsplit": false,
  "onlyNewVersions": false,
  "resetMonitoringState": false,
  "maxConcurrency": 4,
  "requestTimeoutSecs": 25
}
```

# Actor output Schema

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

One row per version: version, date, title, the notes sorted into added, changed, deprecated, removed, fixed, security and breaking lists, whether a breaking-change marker is written, the full notes text, and where the row came from (changelog file, changelog page, GitHub Releases or the PyPI feed). An input that could not be read or split into versions comes back as one free row that says why.

# 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 = {
    "urls": [
        "https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/changelog-to-json").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 = { "urls": ["https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md"] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/changelog-to-json").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 '{
  "urls": [
    "https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md"
  ]
}' |
apify call neverempty/changelog-to-json --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/changelog-to-json"
        }
    }
}
```

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/j1dZlGdakZVd6BSQS/builds/uaZgKSh1LHVmBtkX0/openapi.json
