# Release Notes Index > The latest product releases, indexed for agents. All public documentation pages concatenated below, in sidebar order. Each section is preceded by an HTML comment pointing at its canonical URL. # Why Release Notes Index Release Notes Index is a changelog index built for agents. Add the MCP server to Claude, ChatGPT, Cursor, or your own agent, and "what changed in X since Y" becomes one cheap tool call, as easy as fetching docs. Think of it as the [Context7](https://context7.com) of what shipped. This matters because changelogs have no standard. Teams publish in GitHub releases, CHANGELOG files, marketing blogs, in-app "what's new" panels, and vendor newsletters. The interesting parts rarely live where you'd guess. The Index pulls them into one registry so you can see the full story. ## Motivation - **One feed across sources.** GitHub releases, CHANGELOG files, marketing blogs, release-notes pages, RSS/Atom, and JSON feeds all come out in the same shape: org, product, title, date, summary, categories, tags. The launch post that never got a version tag is in there too. - **Product-level signal, not commit noise.** Releases are grouped by org and product and summarized for humans. A weekly review doesn't drown in dependency bumps and CI tweaks, and it picks up the announcements that GitHub never sees at all. - **With AI, products now change daily.** No human can read it all anymore. The Index gives your agents a live view of what's shipping across the tools you depend on, so research prompts and weekly reviews stay current without manual hunting. - **Input agents can act on, not just report.** As agents get more autonomous, knowing what the rest of the industry is building becomes real input: an agent can look at what's shipping across its field, spot what's emerging, and use that to decide what's worth building next. ## Get started - [Install the CLI](/docs/installation) - [Add the skills](/docs/skills) to your agent - [Browse the examples](/docs/examples) --- # Release Notes Index Changelog index for AI agents and developers. The Index tracks release notes, changelogs, and version updates across hundreds of developer tools, frameworks, and services. You can query it four ways: a CLI, a REST API, an MCP server, and agent skills that trigger on their own. For the motivations behind the project, see [Why](/docs/why). New here? [Install the CLI](/docs/installation), [add the skills](/docs/skills) to your agent, or jump to [Examples](/docs/examples). ## What you can do - **Browse and search** — find releases across organizations and sources by keyword, category, or date - **Track changes** — follow the latest releases from GitHub repos, RSS feeds, and changelog pages - **Integrate** — query the [REST API](/docs/api/rest) directly, plug the [MCP server](/docs/api/mcp) into your agent, install the [skills](/docs/skills) so Claude Code, Codex, Cursor, and OpenCode know how to use it, or publish changelog files [from GitHub Actions](/docs/integrations/github-actions) or [from any CI](/docs/integrations/ci) ## Concepts The Index organizes data in a simple hierarchy: - **Organizations** — companies or teams (e.g., Vercel, Cloudflare) - **Products** — optional grouping within an org (e.g., Vercel → Next.js, Turborepo) - **Sources** — individual changelog feeds (e.g., a GitHub repo, an RSS feed, a changelog page) - **Releases** — individual entries with a title, version, date, and content Each source has a `slug`, a short unique name. Most CLI commands and API endpoints take that slug as their main argument. ## Interfaces | Interface | Best for | | ------------------------------- | ------------------------------------------------------------------ | | **[CLI](/docs/cli/browsing)** | Interactive exploration, fetching, analysis | | **[REST API](/docs/api/rest)** | Programmatic access, web integrations | | **[MCP Server](/docs/api/mcp)** | AI agent tool use (Claude, Cursor, etc.) | | **[Skills](/docs/skills)** | Auto-triggering playbooks for Claude Code, Codex, Cursor, OpenCode | | **Web UI** | Browsing the catalog at [releases.sh](https://releases.sh) | The CLI is open source — see [github.com/buildinternet/releases](https://github.com/buildinternet/releases) (`apps/cli/`). Maintained by [Zach Dunn](https://zachdunn.com) / [Build Internet](https://buildinternet.com). --- # Installation Get the Releases CLI up and running. ## Homebrew On macOS and Linux with [Homebrew](https://brew.sh): ```bash brew install buildinternet/tap/releases ``` ## npm Install globally via npm — prebuilt binaries for macOS, Linux, and Windows (x64) are included: ```bash npm install -g @buildinternet/releases ``` Or run without installing: ```bash npx @buildinternet/releases search "react" ``` ## Shell script (macOS, Linux) Download and install the latest binary directly: ```bash curl -fsSL https://releases.sh/install | bash ``` The script detects your platform, downloads the correct binary from npm, and installs it to `/usr/local/bin`. Set `RELEASES_INSTALL_DIR` to change the install location. Windows users should use npm or the GitHub Releases archives below. ## GitHub Releases (precompiled binaries) Every version ships precompiled binaries for each supported platform on the [`buildinternet/releases` releases page](https://github.com/buildinternet/releases/releases). Use these for air-gapped installs, pinning to a specific version, or machines where npm and Homebrew aren't an option. Available archives per release: - `releases-darwin-arm64.gz` — macOS Apple Silicon - `releases-darwin-x64.gz` — macOS Intel - `releases-linux-arm64.gz` — Linux ARM64 - `releases-linux-x64.gz` — Linux x86_64 - `releases-windows-x64.zip` — Windows x86_64 Each archive ships with a matching `.sha256` file, plus a top-level `checksums.txt` covering the whole release. **macOS / Linux:** ```bash # Pick the archive that matches your platform curl -fsSL -o releases.gz https://github.com/buildinternet/releases/releases/latest/download/releases-darwin-arm64.gz gunzip releases.gz chmod +x releases mv releases /usr/local/bin/ ``` **Windows (PowerShell):** ```powershell Invoke-WebRequest -Uri "https://github.com/buildinternet/releases/releases/latest/download/releases-windows-x64.zip" -OutFile "releases.zip" Expand-Archive -Path "releases.zip" -DestinationPath "." # Move releases-windows-x64.exe somewhere on your PATH and rename to releases.exe ``` ## From source (development) The CLI source lives in the [`buildinternet/releases`](https://github.com/buildinternet/releases) monorepo under `apps/cli/`. Requires [Bun](https://bun.sh) v1.1+ (Bun supports macOS, Linux, and Windows). ```bash git clone https://github.com/buildinternet/releases.git cd releases bun install bun apps/cli/src/index.ts --help ``` ## Verify After installing, verify the CLI is working: ```bash releases --help ``` ## MCP server To use Release Notes Index as an MCP tool server, the easiest path is the hosted remote server at `https://agents.releases.sh/mcp`. Claude (web, desktop, and mobile): add Release Notes Index from the [Claude connector directory](https://claude.ai/directory/releases-index). Codex: ```bash codex mcp add releases --url https://agents.releases.sh/mcp ``` Claude Code: ```bash claude mcp add --transport http releases https://agents.releases.sh/mcp ``` See the [MCP Server](/docs/api/mcp) docs for the general endpoint, client-specific setup, and stdio fallback configuration. ## Environment variables All variables are optional. You don't need any of them for read-only use. Admin commands need a token, which you can also store with `releases auth login`. | Variable | Default | Description | | ------------------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `RELEASES_API_KEY` | _unset_ | Bearer token for `admin` / write commands. Takes priority over a credential stored via `releases auth login` — handy for CI or per-command overrides. | | `RELEASES_API_URL` | `https://api.releases.sh` | Point the CLI at a different API host, e.g. a self-hosted or staging endpoint. | | `RELEASES_INSTALL_DIR` | `/usr/local/bin` | Install location used by the `install.sh` script. | | `RELEASES_TELEMETRY_DISABLED` | _unset_ | Set to `1` to opt out of anonymous telemetry. `DO_NOT_TRACK=1` is also honored — see [Privacy & Telemetry](/docs/privacy). | | `RELEASES_DISABLE_SKILL_UPDATE_CHECK` | _unset_ | Set to `1` to silence the once-a-day "skills are behind, run `releases skills install`" reminder. | | `RELEASES_NO_COMPLETION_HINT` | _unset_ | Set to `1` to silence the shell-completion setup reminder on the landing screen and `--help`. | | `RELEASES_RUN_DIR` | _unset_ | When set, each `admin` write appends a JSONL line to `$RELEASES_RUN_DIR/mutations.jsonl` — an audit trail for agent-driven maintenance batches. | ## Telemetry The CLI and local MCP server record anonymous usage events (command name, CLI version, OS/arch, exit code, duration) to help us understand what's used. No arguments, flag values, queries, or content are ever sent. Opt out any time: ```bash releases telemetry disable # persistent RELEASES_TELEMETRY_DISABLED=1 releases … # per-invocation ``` See [Privacy & Telemetry](/docs/privacy) for the full list of what is and isn't collected. --- # Skills Skills are short, auto-triggering playbooks that teach an AI agent how to use Release Notes Index. When you ask a release-shaped question, your agent loads the matching skill and follows it. They work in Claude Code, Codex, Cursor, OpenCode, and any other agent that supports the [agent skills format](https://agentskills.io/home). ## Install ### Standalone (any agent) Run this from the root of any project where you want the skills, using the [`skills`](https://skills.sh) CLI: ```bash npx skills add buildinternet/releases ``` Your agent picks them up on the next session. ### Claude Code plugin If you're on Claude Code and also want the bundled MCP connection and `/releases` command: ```bash /plugin marketplace add buildinternet/releases ``` ```bash /plugin install releases@releases ``` Claude Code doesn't auto-update third-party plugins by default. To get new skills as they ship, run `/plugin`, open **Marketplaces**, select `releases`, and turn on auto-update. To update once by hand, run `/plugin marketplace update releases` to refresh the listing. Then run `claude plugin update releases@releases --scope user`, using the scope you installed with, and restart Claude Code. ## What the skills do ### releases-mcp Triggers when you ask about a library or product release: "what changed in Next.js 15?", "latest Tailwind releases", "compare Bun vs Deno." Your agent answers from current registry data instead of stale training data. ### releases-cli Triggers when you mention the `releases` CLI or run a `releases` command. Helps your agent pick the right subcommand and flags. ### analyzing-releases Triggers on landscape questions: "what is X shipping lately", "how does X compare to Y", "what's new in observability." Picks a set of products, fetches their recent releases, and summarizes the themes. ### creating-releases-json Triggers when you want to **list your own product**: "add a releases.json", "get indexed on Releases Index", "make us a manifest for our website." It finds where you actually publish release notes, writes a valid v2 manifest, and walks through publishing it at `/.well-known/releases.json`. Install just this skill (or grab the copy buttons on the [submit page](/submit)): ```bash npx skills add buildinternet/releases --skill creating-releases-json ``` See [Listing your product](/docs/listing) for the format and the paste-ready agent prompt. ## Operator skills These are for people running or maintaining the registry itself: onboarding sources (`finding-changelogs`, `managing-sources`), the parse pipeline (`parsing-changelogs`, `classify-media-relevance`), and bulk maintenance (`seeding-playbooks` and friends). They live with the backend in the [releases monorepo](https://github.com/buildinternet/releases) under `.claude/skills/`. Claude Code picks them up automatically in a checkout, or you can install them anywhere: ```bash npx skills add buildinternet/releases ``` Most need an admin API key to do anything. ## Source - **Reader skills** (search, MCP, analysis): [github.com/buildinternet/releases](https://github.com/buildinternet/releases) under `plugins/claude/releases/skills/`. - **Operator skills**: [github.com/buildinternet/releases](https://github.com/buildinternet/releases) under `.claude/skills/`. - **Owner manifest skill** (`creating-releases-json`): [github.com/buildinternet/releases](https://github.com/buildinternet/releases) under `skills/creating-releases-json/`. Two files drive skill grouping and must stay in sync: the skills.sh **web** repo page reads root [`skills.sh.json`](https://github.com/buildinternet/releases/blob/main/skills.sh.json) ([skills.sh customize](https://www.skills.sh/docs/customize)), while the `npx skills add` **CLI** picker groups by plugin from [`.claude-plugin/marketplace.json`](https://github.com/buildinternet/releases/blob/main/.claude-plugin/marketplace.json) (each `plugins[].name` becomes a picker group). --- # Examples Every command prints a readable table by default. Add `--json` for structured output that scripts and agents can parse. The release readers (`search`, `tail`/`latest`, `get`) return a [slim JSON shape](/docs/cli/browsing#slim-release-json) by default to keep token usage low. Add `--full` when you need everything. ## Stay up to date See what shipped recently for a source, product, or organization. ## Find what you need Search works across organizations, sources, and releases. It's hybrid: full-text search for exact keywords, plus semantic search that matches by meaning. Each result includes a content preview, so you can find the right release without opening it. --- # How to find any product's changelog Changelogs have no standard home. Teams publish in GitHub releases, `CHANGELOG.md` files, marketing blogs, documentation sites, in-app "what's new" panels, and vendor newsletters — and the interesting entries rarely live where you'd guess. This guide gives you a repeatable method for finding the best source for any product, whether you're a person hunting for release notes or an agent that needs structured data. The short version: guess the obvious URLs first, then look for a feed, then check GitHub, and only scrape a rendered page as a last resort. Or skip the hunt — [Release Notes Index](/) has already run this method across hundreds of products, and you can [query the result](#the-shortcut-query-an-index-that-already-did-this) for free. ## The method, in priority order Work down this list and stop at the first hit. Each step trades a little more effort for a little less structure. ### 1. Try the obvious URLs Most products that publish a changelog put it at a guessable address. Try these against the product's domain, in roughly descending order of popularity: - **Paths:** `/changelog`, `/updates`, `/releases`, `/release-notes`, `/whats-new`, `/news` — and the same paths under a docs site (`/docs/changelog`, `/docs/release-notes`). - **Subdomains:** `changelog.{domain}` and `updates.{domain}`. - **Root files**, for developer-oriented projects: `/changelog.md` or `/changelog.txt` on the domain. A site search (`site:example.com changelog OR "release notes" OR "what's new"`) catches the stragglers — products that bury updates under a blog tag or a help-center section. If the product is multi-product (one company, several tools), expect per-product changelogs: check the product's own docs site, not just the company domain. ### 2. Look for an RSS or Atom feed A feed is the best source you'll commonly find: structured entries, stable dates, no parsing of rendered HTML. Two ways to find one: - **Feed autodiscovery.** View the changelog page's HTML source and look for `` (or `rss+xml`) in the ``. - **Well-known paths.** Try the common ones against the changelog URL and the site root: `/feed`, `/feed.xml`, `/rss.xml`, `/atom.xml`, `/changelog.rss`, `/changelog/rss.xml`, `/rss/`. The changelog platform often gives the path away. Sites hosted on Mintlify serve `/rss.xml`; ReadMe and Fern serve `/changelog.rss`; Docusaurus blogs serve `/blog/rss.xml`; Ghost serves `/rss/`; WordPress serves `/feed/`. If you can identify the platform (page footer, HTTP headers, HTML patterns), try its known path first. **Verify the feed is actually the changelog.** This is the step most people skip, and it's where feed discovery goes wrong. Sample a few entries and check that they describe product changes. Red flags: - Entry URLs point at `/blog/` rather than `/changelog/` or `/releases/` - Titles read like articles ("Choosing a logging library: the definitive guide") - No version numbers or feature/fix language anywhere - The feed is site-wide (`/feed.xml` at the root) rather than scoped to the changelog section A site-wide blog feed that happens to include release posts will bury them in marketing. Keep looking for a section-scoped feed before settling for it. ### 3. Check GitHub For developer tools, GitHub often holds the richest history — sometimes richer than the official page: - **Tagged releases**: `github.com/{owner}/{repo}/releases`, available as structured data from the [Releases API](https://docs.github.com/en/rest/releases/releases) and as a built-in Atom feed at `github.com/{owner}/{repo}/releases.atom`. - **The `CHANGELOG.md` file** at the repo root (also `CHANGES.md`, `HISTORY.md`, `RELEASES.md`, or `NEWS.md`). Many projects write entries here that never become tagged releases, so check it even when tagged releases exist. The catch: GitHub only covers what ships through the repo. Launch announcements, pricing changes, and hosted-product updates usually appear on the company's site and never get a tag. For a full picture of a commercial product, you typically need both the GitHub source and the website changelog. ### 4. Last resort: the rendered page If there's no declaration, no feed, and no repo, you're left with the changelog page itself. Before committing to scraping, look for an escape hatch in the page source: a `.md` view of the page, a feed URL buried in JavaScript, or a link to the underlying markdown on GitHub. Docs platforms frequently expose one of these even when nothing is advertised. If nothing structured exists, the page must be scraped and diffed on a schedule — fragile, and hostile to automate from scratch. Watch out for two traps: client-rendered pages that return an empty app shell to plain HTTP requests, and bot-protection challenges that block automated fetching entirely. This is precisely the case where an existing index earns its keep. ### Worth one extra request: machine-readable declarations A small but growing number of products declare their changelog location explicitly: a [`/.well-known/releases.json`](/docs/listing) or `/.well-known/changelog.json` manifest on the domain, or a `` tag in the homepage's ``. Adoption is still low, so don't lead with this — but the check costs one request, and when it hits it's the publisher stating intent, not a heuristic. If you're automating discovery, run it alongside step 1. (If you own a product, [declaring one](/docs/listing) makes every tool in this guide work on the first try.) ## The shortcut: query an index that already did this Releases Index runs this method — declarations, feed discovery, provider detection, GitHub, and page monitoring for the holdouts — across hundreds of developer tools and services, then normalizes everything into one registry: org, product, title, date, summary, categories. Most reads are public, with no account or API key. **On the web:** [search](/search) for the product, or browse the [catalog](/catalog). Every org page collects all of its sources — the GitHub releases and the marketing changelog side by side. **From a domain:** if all you have is `vercel.com`, resolve it directly: ```bash curl "https://api.releases.sh/v1/lookups/by-domain?domain=vercel.com" ``` **From the command line:** ```bash npm install -g @buildinternet/releases # or: brew install buildinternet/tap/releases releases search "tailwind" # find the org and its sources releases tail tailwindcss # latest releases from one source releases tail --org vercel # latest across an org ``` **For agents:** add the hosted MCP server and "where is X's changelog, and what changed?" becomes one tool call: ```bash claude mcp add --transport http releases https://agents.releases.sh/mcp ``` Or install the [agent skills](/docs/skills) so Claude Code, Codex, Cursor, and OpenCode reach for the CLI on their own: `npx skills add buildinternet/releases`. **As a feed:** every org, source, and collection page serves Atom — append `.atom` to its URL. See [Turn any changelog into an RSS feed](/docs/guides/changelog-rss-feed). If a product isn't indexed yet, [submit it](/submit) — or, if it's yours, [get listed](/docs/listing). ## FAQ ### Is there a standard format for changelogs? For the file itself, [Keep a Changelog](https://keepachangelog.com) is the closest thing to a convention, and only for projects that maintain a `CHANGELOG.md`. For _discovery_ — telling machines where your changelog lives — emerging pieces like [`/.well-known/releases.json`](/docs/listing) and `` exist (see above), but adoption is thin. In practice the de facto standard is the guessable URL: `/changelog` or `/updates` on the product's domain. ### What's the difference between a changelog and release notes? In practice, nothing reliable. "Changelog" leans toward exhaustive, versioned lists; "release notes" leans toward curated highlights per release. Companies use the terms interchangeably, and the same product may have both. When hunting, search for both terms, plus "what's new" and "product updates". ### How do AI agents check changelogs? Three common patterns: fetch and parse a feed or `CHANGELOG.md` directly (works per-product, breaks on scrape-only sites), call GitHub's Releases API (works for repos only), or query an aggregator over MCP or REST so one integration covers every product. The MCP route is the cheapest in tokens: the index has already extracted titles, dates, and summaries, so the agent doesn't burn context parsing HTML. ### Why doesn't every product have a feed? Many changelog platforms (Notion, GitBook, Intercom, and most custom-built pages) simply don't emit one. The publisher would have to add it deliberately, and most never do — which is why a method that ends in "someone monitors the rendered page for you" is unavoidable for full coverage. ### The changelog page exists but is always behind the product's actual releases. Now what? Common with marketing-managed changelogs. Add the product's GitHub repo (step 3) as a second source — tags land at ship time even when the page lags. This is exactly why the Index tracks multiple sources per org. --- # Turn any changelog into an RSS feed Plenty of changelogs ship without a feed: the page updates, but there's nothing for a feed reader, a Slack integration, or a script to subscribe to. This guide gets you a working Atom feed for any product's changelog in two steps — check whether a real feed already exists, and if not, subscribe through an index that monitors the page for you. (Atom and RSS are sibling formats; every feed reader speaks both. The feeds below are Atom.) ## Step 1: check for a native feed If the publisher already emits a feed, use it — it's first-party and updates the moment they publish. Two quick checks: - View the changelog page's HTML source and look for `` (or `rss+xml`) in the ``. - Try the common paths against the changelog URL: `/feed`, `/feed.xml`, `/rss.xml`, `/atom.xml`, `/changelog.rss`, `/rss/`. For GitHub projects there's always one: `github.com/{owner}/{repo}/releases.atom` covers tagged releases, no API key needed. If a feed turns up, spot-check that its entries are actually release content and not the company's whole blog — a site-wide `/feed.xml` full of marketing posts isn't a changelog feed. The [finding-a-changelog guide](/docs/guides/find-a-changelog) covers feed discovery and verification in more depth. ## Step 2: no feed? Append `.atom` to a Releases Index page The Index monitors changelogs across hundreds of developer tools and services — including changelog pages with no feed of their own — and serves every org, source, and collection as an Atom feed. Find the product on [releases.sh](/) and append `.atom` to the page URL. No account, no API key. **An organization** — everything a company ships, across all of its changelogs, GitHub repos, and blogs: ``` https://releases.sh/anthropic.atom ``` **A single source** — one specific changelog, when the whole org is too noisy: ``` https://releases.sh/anthropic/claude-code.atom ``` **A collection** — a curated group of related products in one feed, such as every major application platform: ``` https://releases.sh/collections/application-platforms.atom ``` Browse [collections](/collections) for the full list, or use [search](/search) to find the org or source page you want. The same suffix trick works for other formats: `.json` for structured data and `.md` for an LLM-friendly view. Entries carry the release title, publish date, and a summary, normalized to the same shape regardless of where the release was published — a GitHub tag, an RSS item, or a plain changelog page look identical to your reader. ### How fresh is it? Feeds reflect the index. Sources are polled on a rolling schedule (a few hours for active sources), so entries appear shortly after the publisher posts them rather than at the same instant. For most "tell me when X ships" uses that's indistinguishable from native; if you need push-latency delivery, use a webhook instead (below). ## If the product isn't indexed yet Two paths: - **Anyone** can [submit a product](/submit) to be tracked. - **Product owners** can [get listed](/docs/listing) directly — publishing a small `releases.json` manifest on your domain declares where your changelog lives, and doubles as a native discovery signal for any tool, not just this one. ## Beyond feeds A feed is the lowest-friction subscription, but not the only one — [How to get notified when products ship updates](/docs/guides/release-notifications) covers the push channels in depth: - **Webhooks** — sign in, follow the orgs you care about, and get a `release.created` POST to your endpoint in real time. See the [webhooks docs](/docs/api/webhooks). - **REST API** — poll `https://api.releases.sh/v1/releases/latest` with filters for org, source, or date. See the [REST API docs](/docs/api/rest). - **MCP for agents** — `claude mcp add --transport http releases https://agents.releases.sh/mcp` gives an agent tools to check what changed on demand, no subscription at all. ## FAQ ### Is this free? Yes. Feeds, like all public reads on the Index, need no account or API key. ### RSS or Atom — does it matter? No. Atom is the slightly stricter, younger sibling of RSS, and every feed reader, automation platform, and RSS library handles it. If a tool asks for an "RSS URL", paste the `.atom` URL. ### Can I get one feed for several products? Yes, two ways: an org feed already merges every source under that company, and [collections](/collections) merge related products across companies into one feed. For an arbitrary custom set, add each source's `.atom` URL to your reader separately — folders in the reader do the merging. ### Why not just scrape the page myself on a cron? You can, but budget for the failure modes: client-rendered pages that return an empty shell to plain HTTP requests, bot-protection challenges, layout changes that silently break your parser, and diffing logic to avoid re-alerting on old entries. That's the machinery an index maintains so you don't have to. ### The feed exists but my reader shows old entries at the top Readers sort by the entry's publish date. When an index first ingests a source it may backfill history, and some publishers post entries with earlier dates. Both settle out after the first fetch; sort by date in your reader if it doesn't. --- # How to get notified when products ship updates Checking changelogs by hand doesn't scale past two or three products. This guide covers every way to flip that around — the update comes to you: a Slack or Discord message when something ships, an email digest on your schedule, a feed in your reader, or a signed webhook into your own systems. The options form a ladder from zero-setup to fully programmable. Most of the Index-powered ones share one primitive: [follow](/following) the orgs and products you care about (a free account), and every channel below delivers that same follow list. ## What the publisher already offers Before adding anything, check the two native options: - **GitHub's "Watch → Custom → Releases"** on a repo emails you (or notifies in-app) on every tagged release. Solid for libraries; useless for hosted products whose updates never get a tag. - **The vendor's own newsletter or in-app "what's new" panel.** Coverage is whatever marketing decides to send, on their schedule. Both are per-product and per-channel. Everything below gives you one subscription across your whole stack. ## RSS: the quiet default If you already live in a feed reader, subscribe to feeds and you're done — no account needed. Every Releases Index org, source, and collection page serves Atom by appending `.atom` to its URL (`releases.sh/anthropic.atom`), including changelogs that publish no feed of their own. The [RSS guide](/docs/guides/changelog-rss-feed) covers this end to end. If you'd rather have one feed than many: sign in, follow your orgs, and generate a **personal feed token** on [Notifications](/account/notifications) — a private Atom URL of everything you follow, updated as your follows change. RSS is pull, not push: your reader decides when to check. For "tell me the moment it ships", keep going. ## Slack notifications The most common ask — "post new releases to our team channel" — takes two steps and no app install: 1. Create a [Slack incoming webhook](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/) for the channel. 2. On [Notifications](/account/notifications), pick **Slack** in the **Slack & Discord** section, paste the URL, and connect. Each release you follow arrives as a compact message: linked title, short summary, org avatar, and date. There's a Test button to confirm the wiring. Full details, including why the webhook URL must stay secret: [Send releases to Slack](/docs/integrations/slack). ## Discord notifications Same two-step setup as Slack — no bot to install: 1. Create a [Discord incoming webhook](https://support.discord.com/hc/en-us/articles/228383668) for the channel. 2. On [Notifications](/account/notifications), pick **Discord** in the **Slack & Discord** section, paste the URL, and connect. Each release you follow arrives as a compact embed: linked title, short summary, org avatar, and date. There's a Test button to confirm the wiring. Full details, including why the webhook URL must stay secret: [Send releases to Discord](/docs/integrations/discord). The Notifications page holds one connection for everything you follow, so pick Slack or Discord there. To post one organization's releases to another channel, add an org webhook with the Slack or Discord format on [Webhooks & API](/account/webhooks). Microsoft Teams, Mattermost, and other chat tools still use an RSS bot on a `.atom` URL, or a [signed webhook](#webhooks-for-your-own-systems) plus a small relay. ## Email digest For a rollup instead of a stream: turn on the **release digest** on [Notifications](/account/notifications) and pick daily or weekly. You get one email summarizing what shipped across everything you follow — the right cadence when you want awareness, not interruptions. ## Webhooks, for your own systems When the destination is your code — an internal tool, a database, a bot you're building — subscribe directly. Each new release you've scoped to arrives as a signed `POST` with a `release.created` payload: ```bash curl -X POST https://api.releases.sh/v1/me/webhooks \ -H "Authorization: Bearer $RELEASES_TOKEN" \ -H "Content-Type: application/json" \ -d '{"scope":"follows","url":"https://hooks.example.com/my-follows"}' ``` Subscriptions come in two scopes — one URL for everything you follow, or per-org with filters (`sourceSlug`, `productSlug`, `releaseType`) — with HMAC signatures, retries, delivery logs, and a test endpoint. The fastest path for one org is **Add webhook** in that organization's ⋯ menu, which opens [Webhooks & API](/account/webhooks) already scoped to it. See the [webhooks docs](/docs/api/webhooks) for the contract. ## For agents An agent usually shouldn't sit on a push channel — it should check on demand. Add the MCP server and "what changed in my stack this week?" becomes a tool call: ```bash claude mcp add --transport http releases https://agents.releases.sh/mcp ``` For a live terminal view, the CLI can follow the stream directly: `releases tail -f` polls for new releases as they land. And a follows-scoped webhook works as an agent trigger too — new release in, agent run out. ## Which one should I use? | You want | Use | | ---------------------------------- | -------------------------------------------------------- | | Updates in a feed reader | `.atom` feeds, or a personal feed token for your follows | | A message in a team Slack channel | Slack on [Notifications](/account/notifications) | | A message in a Discord channel | Discord on [Notifications](/account/notifications) | | A message in Teams or another chat | An RSS bot on a `.atom` URL, or a webhook + relay | | One summary email a day or week | The release digest | | Events into your own code | Signed webhooks (`/v1/me/webhooks`) | | An agent that checks on demand | The MCP server or CLI | ## FAQ ### How fast are the notifications? Push channels (Slack, Discord, webhooks) fire when a release is indexed, which follows the source's polling schedule — typically within a few hours of the publisher posting, often much sooner for active sources. RSS adds your reader's own refresh interval on top. The digest batches to its cadence by design. ### Can I cut the noise down? Several dials: follow specific products instead of whole orgs, subscribe to a single source's feed instead of the org's, or set `releaseType: "feature"` on a webhook to skip rollup-style entries like routine version bumps. ### Do I need an account? Only for the follow-based channels (Slack, Discord, digest, personal feed, webhooks). Public `.atom`, `.json`, and `.md` feeds and the MCP server's read tools work with no account at all. ### What if a product I want isn't indexed? [Submit it](/submit), or if it's your product, [get listed](/docs/listing). Once a source is in the index, every channel on this page works for it — including changelogs that publish no feed or API of their own. ### Can one webhook feed multiple channels? Yes — that's the relay pattern. One follows-scoped webhook into a small worker or automation platform can fan out to Discord, Teams, a database, and a dashboard at once, with your own formatting per destination. --- # Listing your product `releases.json` is a small file on your domain that tells the registry **where you publish product updates**: a changelog, feed, GitHub Releases, App Store listing, or hosted CHANGELOG file. Where the file lives decides what it speaks for. A file on your domain speaks for your company; a file at a repo root speaks for that repo only. When the file is live, [check and activate on the submit page](/submit). If the notes themselves live in git, you can [publish from GitHub Actions](/docs/integrations/github-actions) or [from any other CI](/docs/integrations/ci) so a push updates your releases right away, without waiting for our next check. ## Fast track: let an agent write it Install the skill, paste the prompt (swap in your website), publish the file it produces at `/.well-known/releases.json`, then activate: ```bash npx skills add buildinternet/releases --skill creating-releases-json ``` ```text Create a releases.json manifest for our product so registries and agents can find where we publish release notes. 1. Install the creating-releases-json skill: npx skills add buildinternet/releases --skill creating-releases-json 2. Follow that skill end-to-end: discover the real places we publish updates (changelog, feed, GitHub Releases, App Store, CHANGELOG file), write a valid v2 manifest, and show me the finished file plus the URL it should be served from (usually https://{domain}/.well-known/releases.json). 3. Only declare locations that actually exist — never invent URLs. Our website is: ``` The same install command and prompt are on the [submit page](/submit) with one-click copy. ## Write it yourself Host this at `https://yourdomain.com/.well-known/releases.json`: ```json { "$schema": "https://releases.sh/schemas/releases.json", "version": 2, "releases": [ { "url": "https://updates.acme.com", "feed": "https://updates.acme.com/rss.xml" } ] } ``` Each `releases[]` entry needs **at least one** locator: | Key | Use it for | | ---------- | --------------------------------------------------------------- | | `url` | Page a person reads (changelog / what's-new) | | `feed` | RSS/Atom feed | | `github` | Repo Releases — `"acme/cloud-sdk"` (or `"self"` in a repo file) | | `appstore` | App Store listing | | `file` | Hosted raw changelog (e.g. `CHANGELOG.md` URL) | Optional: `title` on a location; `"canonical": true` on the primary one per scope. ### Publish it yourself If the changelog lives in git and you will push it with the [GitHub Action](/docs/integrations/github-actions), mark that entry instead of asking us to poll the repo: ```json { "github": "acme/docs", "path": "changelog/**/*.mdx", "publish": "push" } ``` `path` is the file (`CHANGELOG.md`) or glob (`changelog/**/*.mdx`) your publisher reads — the GitHub Action's `changelog-path` / `changelog-glob`, or the same path you diff in any other CI. We create that source only after you verify domain ownership. Checking the domain then lists the next steps: verify, mint a publish token, add the Action (or [post the batch yourself](/docs/integrations/ci)). Until then the locator stays declared and nothing is polled. ### Multiple products Add `products[]` only when each product has **its own** release location. If everything shares one changelog, keep it as one entry. Company fields (`name`, `description`, `category`, `avatar`, `social`, `tags`) are optional. ```json { "$schema": "https://releases.sh/schemas/releases.json", "version": 2, "name": "Acme", "products": [ { "name": "Acme Cloud", "releases": [ { "url": "https://acme.com/whats-new", "feed": "https://acme.com/whats-new/rss.xml", "canonical": true }, { "github": "acme/cloud-sdk" } ] } ], "releases": [{ "url": "https://updates.acme.com" }] } ``` Top-level `releases` and `products` can coexist (company firehose + per-product streams). ### In a repo Repo-root `releases.json` binds **that repo** only (`product` + `releases[]`). Use `{ "github": "self" }` when this repo's GitHub Releases are the record. ```json { "$schema": "https://releases.sh/schemas/releases.json", "version": 2, "product": { "name": "Acme Cloud", "slug": "acme-cloud" }, "releases": [ { "url": "https://acme.com/whats-new", "canonical": true }, { "github": "self" } ] } ``` ## After you publish 1. [Submit → check your domain](/submit) and activate if you're unlisted. 2. The registry re-reads the file on a regular sweep; later edits land without re-submission. 3. **Feeds / GitHub / App Store** go live after a quick automated check. **Plain web pages** get a quick review from our team before we start checking them. 4. The registry only fills in blanks. It never overwrites curator or editorial fields, and an invalid or missing file changes nothing. No manifest yet? [Suggest a changelog URL](/submit) for curator review. ## Add a badge Once you verify your domain, the claim panel on your organization's page offers a small Release Notes Index badge, in light and dark, with a Markdown snippet for your README and an HTML snippet for your site. The badge links to your organization's page: ```md [![Release Notes Index](https://releases.sh/badges/listed.svg)](https://releases.sh/your-org) ``` Replace `your-org` with your organization's slug, or copy the snippet from the claim panel. Use `listed-dark.svg` for dark backgrounds. ## Reference - **`version: 2` required.** v1 is no longer read. - **Limits:** ≤24 products, ≤8 locations per product, ≤32 locations total per file. - **`$schema`:** optional; [published schema](https://releases.sh/schemas/releases.json) for editors. - **Pin to registry IDs** (optional): `"registries": { "releases.sh": { "org": "org_…" } }` — IDs are on org/product pages. - **Live example:** [releases.sh/.well-known/releases.json](https://releases.sh/.well-known/releases.json). --- # Send releases to Slack Get a Slack message every time an org or product you follow ships a release. There's no app to install. You paste a Slack **incoming webhook URL** and Release Notes Index posts to it. ## 1. Create a Slack incoming webhook In Slack, create an [incoming webhook](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/) for the channel you want releases posted to. Slack gives you a URL that looks like `https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXX`. ## 2. Connect it on Releases Index Open [Notifications](/account/notifications), find the **Slack & Discord** section, pick **Slack**, paste the webhook URL, and click **Connect**. That's it. You'll get a Slack message for everything you follow. Use the **Test** button to post a sample message and confirm the channel is wired up. ## What the message looks like Each release is posted as a compact Slack message: a linked title, a short summary, and a context line with the organization's avatar and date. ## Keep the URL private Slack webhooks are **unsigned**: the URL itself is the secret. No signing key is issued and no signature headers are sent. Treat the URL like a password. To rotate it, remove the Slack connection and create a new one with a fresh URL. ## Supported hosts The URL host must be `hooks.slack.com` (standard and Enterprise Grid) or `hooks.slack-gov.com` (GovSlack). Other hosts are rejected. ## Need more control? For org-specific alerts, release-type filters, or the raw signed JSON payload, use [Webhooks & API](/account/webhooks). --- # Send releases to Discord Get a Discord message every time an org or product you follow ships a release. There's no bot to install. You paste a Discord **incoming webhook URL** and Release Notes Index posts to it. ## 1. Create a Discord incoming webhook In Discord, open the channel's **Edit Channel → Integrations → Webhooks** (or Server Settings → Integrations → Webhooks) and create a webhook. Discord gives you a URL that looks like `https://discord.com/api/webhooks/123456789012345678/abcdefghijklmnopqrstuvwxyz`. See Discord's [intro to webhooks](https://support.discord.com/hc/en-us/articles/228383668) if you haven't created one before. ## 2. Connect it on Releases Index Open [Notifications](/account/notifications), find the **Slack & Discord** section, pick **Discord**, paste the webhook URL, and click **Connect**. You'll get a Discord message for everything you follow. Use the **Test** button to post a sample embed and confirm the channel is wired up. For a single organization instead, open [Webhooks & API](/account/webhooks), set **Format** to **Discord message**, choose **Org**, and click **Create**. ## What the message looks like Each release is posted as a compact Discord embed: a linked title, a short summary, and the organization's name and avatar. Discord localizes the timestamp to each viewer. ## Keep the URL private Discord webhooks are **unsigned**: the URL itself is the secret. No signing key is issued and no signature headers are sent. Treat the URL like a password. To rotate it, remove the Discord webhook and create a new one with a fresh URL. ## Supported hosts The URL host must be `discord.com` (or the legacy `discordapp.com`, `canary.discord.com`, `ptb.discord.com`) and the path must be `/api/webhooks/{id}/{token}`. Other hosts and the Slack-compat `/slack` suffix are rejected — this format posts a native Discord embed, not a Slack payload. ## Need more control? For org-specific alerts, release-type filters, or the raw signed JSON payload, stay on [Webhooks & API](/account/webhooks) and switch **Format**. --- # Publish from GitHub Actions If your release notes live in git — `CHANGELOG.md`, Keep a Changelog, a dated product log, or one MDX file per release — a GitHub Action can create or update the matching releases in Release Notes Index the moment the file lands on your default branch. You don't wait for us to check your changelog. Not on GitHub? The Action posts a normal batch write. [Publish from any CI](/docs/integrations/ci) has the `curl` recipe and a GitLab example. `releases publish` runs the same plan from the [CLI](/docs/installation) (0.83.0 or later). Re-running the same commit is safe. Each entry is keyed by a stable URL, and the batch upsert only writes when the body actually changed. The Action has two modes: - **Single-file** (default) — one changelog file with `##` sections. - **Directory** — one MDX or Markdown file per release, metadata in YAML frontmatter. Used by sites built with Mintlify, Fumadocs, Docusaurus, Astro content collections, or Nextra. ## 1. Create a token The Action calls `POST /v1/sources/…/releases/batch`. Two kinds of `relk_…` token work: - **A publish token** (the usual choice). If you've verified you own your domain (an ownership claim through the listing flow), you can mint one yourself. It can publish to one source and nothing else: no source edits, no other sources. It stops working if you revoke it or lose the claim. - **A write-scoped machine token**, issued by a Release Notes Index admin. To mint a publish token, sign in and open [Account → Webhooks & API](/account/webhooks). Under **Publish tokens**, pick the source and create the token. Copy it right away. It won't be shown again. The panel also gives you the workflow step to paste, lists your tokens with when each was last used, and revokes them. Or from the terminal, with the [CLI](/docs/installation) (0.81.0 or later). Each command asks you to approve it in the browser, then signs out. `create` prints only the token, so you can pipe it straight into a repository secret: ```bash releases publish-token create --source src_… | gh secret set RELEASES_API_TOKEN releases publish-token list releases publish-token revoke ``` Read-only user keys (`relu_…`, including `releases login`) are rejected. Store the token as a repository secret named `RELEASES_API_TOKEN`. ## 2. Point it at a source You need the source the Action should write to — preferably the typed id (`src_…`) from the source page or `releases admin source list`. A slug works if you also pass the organization. When you publish with a publish token, the first successful run marks the source as **push-fed**: we stop polling it, since your Action is now how it gets new content. The source page then shows a "Last Published" time instead of "Last Checked". If you still want us to poll it as well, ask us to set it to keep polling; the Action won't switch it back. With a machine token from an admin, the admin sets this by hand (`PATCH /v1/sources/:slug/metadata` with `{"ingestMode": "push"}`). ## 3. Add the workflow Copy the committed example at [`actions/publish-changelog/examples/publish-changelog.yml`](https://github.com/buildinternet/releases/blob/main/actions/publish-changelog/examples/publish-changelog.yml) into your repo's `.github/workflows/`. Replace the `source` id and `RELEASES_API_TOKEN` secret. Do not copy this repo's dogfood `.github/workflows/changelog-publish.yml` — that file pins our source and updates URL. Checkout with full history so the Action can diff against the previous commit, then call the Action: ```yaml name: Publish changelog on: push: branches: [main] paths: - CHANGELOG.md # or: docs/**, .changeset/** permissions: contents: read jobs: publish: runs-on: ubuntu-latest timeout-minutes: 10 steps: - uses: actions/checkout@v6 with: fetch-depth: 0 persist-credentials: false - uses: buildinternet/releases/actions/publish-changelog@main with: source: src_… api-token: ${{ secrets.RELEASES_API_TOKEN }} ``` `fetch-depth: 0` is required. A shallow clone cannot `git show`/`git diff` against `github.event.before`. ## Inputs | Input | Required | Notes | | ------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `source` | yes | `src_…` id, or a source slug (then set `org`) | | `org` | no | Organization slug for the org-scoped batch route | | `api-token` | yes | A publish token for this source, or a write-scoped `relk_…` | | `api-url` | no | Defaults to `https://api.releases.sh` | | `changelog-path` | no | Single-file mode. Defaults to `CHANGELOG.md`. Must stay default when `changelog-glob` is set. | | `changelog-glob` | no | Directory mode. e.g. `changelog/**/*.mdx`. See [Directory mode](#directory-mode-mdx-and-markdown). Cannot combine with a non-default `changelog-path`. | | `working-directory` | no | Subdirectory in a monorepo | | `url-template` | no | See [Stable URLs](#stable-urls) | | `generate-content` | no | Defaults to `true`. Admin tokens regenerate summaries for edited entries; write and publish tokens skip this (batch already queues the fill for new rows) | Path filters stay on your workflow `on.push.paths` — the Action does not decide which pushes run. ## Changelog formats (single-file mode) The Action reads `##` headings: - **Versioned** — Keep a Changelog / conventional-changelog, e.g. `## [1.4.0] - 2026-05-01`. `[Unreleased]` is skipped. - **Date-sectioned** — `## June 10, 2026`, newest first. This is the format [releases.sh](https://releases.sh/updates) publishes. Other heading styles are ignored. If the file changed but nothing parsed, the Action fails so a silent no-op cannot hide a format mistake. ## Directory mode (MDX and Markdown) Some sites keep one file per release instead of a single changelog: a `changelog/` folder of dated MDX files (Mintlify), or a blog-style folder per entry with a `slug` field (Fumadocs, Docusaurus, Astro, Nextra). Set `changelog-glob` instead of `changelog-path` to read that layout — the two options are mutually exclusive. ```yaml - uses: buildinternet/releases/actions/publish-changelog@main with: source: src_… api-token: ${{ secrets.RELEASES_API_TOKEN }} changelog-glob: changelog/**/*.mdx ``` The Action reads each matched file's YAML frontmatter. The first field that matches wins: - **title** — `title`, else the first `# ` heading in the body, else the filename - **date** — `date`, `publishedAt`, `published`, or `pubDate` - **version** — `version` - **slug** (the stable key) — `slug`, else the file's path minus its extension, relative to the glob's static base directory (the part of the glob before the first wildcard) - **url** — `url` or `canonical`, else `url-template`, else the file's GitHub blob URL A file with `draft: true` in its frontmatter is skipped — never published, never counted. The body is turned into plain markdown: `import`/`export` lines are removed, JSX components collapse to their text content (`Hi` becomes `Hi`), and ``/`` tags become markdown images. Fenced code blocks, ordinary markdown, links, and images pass through unchanged. This is a conservative line-based pass, not a full MDX compiler — unusual constructs (a multi-line `import`, a custom self-closing component other than an image) can slip through untouched, so check the first run's published body. **Change detection** comes from `git diff --name-status` between `before-sha` and the current commit, filtered to the glob. Added and modified files are upserted; a rename lands as a modified entry at its new path; a deleted file is reported in the `deleted` output and the logs but is **not** removed from Releases Index — deleting a release is a decision for a curator, not something a push should do automatically. On the very first push (no earlier commit to diff against), every file matching the glob is published, the same way single-file mode publishes an entire changelog the first time it sees one. ## Stable URLs Idempotency is `(source, url)`. In single-file mode the Action prefers a permalink already in the heading (common on conventional-changelog compare links). In directory mode a frontmatter `url`/`canonical` wins. Otherwise both modes fall back to `url-template`. Placeholders: `{key}` (version, `YYYY-MM-DD`, or the directory-mode slug), `{version}`, `{date}`, `{path}`, `{slug}` (directory-mode alias for `{key}`). Default when you omit the template: ```text https://github.com///blob//#{key} # single-file mode https://github.com///blob//{path} # directory mode ``` To keep human URLs on a docs site: ```yaml url-template: https://example.com/changelog/{slug} ``` Do not put the commit SHA in the URL. A later edit to the same version (or the same file) would insert a second release instead of updating the first. ## What happens on the server The Action does **not** invent a second write path. It posts the same batch body the rest of ingest uses: ```http POST /v1/sources/{source}/releases/batch { "mode": "upsert-content", "releases": [ { "title", "content", "url", "publishedAt", "type" } ] } ``` That upsert triggers the usual side effects: content generation for new rows, embeddings, live events, and web revalidation. An identical re-run returns `inserted: 0`. ## Secrets Treat `RELEASES_API_TOKEN` like a password. Rotate it if it leaks. The Action never prints the token. --- # Publish from any CI The [GitHub Action](/docs/integrations/github-actions) is one client of a single write: `POST /v1/sources/{source}/releases/batch` with `mode: "upsert-content"`. GitLab CI, Buildkite, CircleCI, or a docs build can post that same body. There is no second ingest path. Re-running the same entries is safe. Each release is keyed by a stable URL, and the batch upsert only writes when the body actually changed. An identical re-run returns `inserted: 0`. `releases publish` (in the [CLI](/docs/installation), 0.83.0 or later) builds this body for you. It reads a single file of `##` sections or one MDX file per entry, diffs with `--since `, and prints the JSON with `--dry-run`. It uses the same planner as the Action. If you can't install the CLI in your job, post the JSON yourself with `curl` (step 4). ## 1. Create a token The request needs a Bearer token that can write this source: - **A publish token** (the usual choice). After you verify domain ownership, mint one under [Account → Webhooks & API](/account/webhooks). It can publish to one source and nothing else. - **A write-scoped machine token**, issued by a Release Notes Index admin. Read-only user keys (`relu_…`, including `releases login`) are rejected. Store the token as `RELEASES_API_TOKEN` in CI. Treat it like a password. The token steps, including `releases publish-token create`, are on [Publish from GitHub Actions](/docs/integrations/github-actions#1-create-a-token). ## 2. Point it at a source Use the typed source id (`src_…`). A slug works on the org-scoped route if you also know the organization: `POST /v1/orgs/{org}/sources/{slug}/releases/batch`. The first successful write made with a publish token marks the source as **push-fed**: we stop polling it, because your CI is now how it gets new content. The source page shows "Last Published" instead of "Last Checked". If the changelog lives in a git repo, say so in [`releases.json`](/docs/listing) with a push locator. `publish` is only `"push"`, and it needs both `github` and `path` (the file or glob your CI reads): ```json { "github": "acme/docs", "path": "changelog/**/*.mdx", "publish": "push" } ``` We create that source only after the domain ownership claim is verified. An unverified manifest leaves the locator declared and does not flip an existing source to push. `path` with a `*` is directory mode (`changelog-glob` on the Action); a path with no wildcard is one file (`changelog-path`). ## 3. Publish with the CLI `releases publish` reads `RELEASES_API_TOKEN` and posts only the entries that changed since the commit you pass: ```bash releases publish --source "$RELEASES_SOURCE" --since "$CI_COMMIT_BEFORE_SHA" \ --url-template "https://gitlab.com/acme/app/-/blob/main/CHANGELOG.md#{key}" ``` - `--source` takes a `src_…` id, an `org/slug` coordinate, or a slug with `--org`. - `--changelog ` picks the file (default `CHANGELOG.md`). `--glob "changelog/**/*.mdx"` switches to one file per entry. `--cwd` points at a subdirectory in a monorepo. - `--url-template` sets each entry's permalink. Placeholders: `{key}`, `{version}`, `{date}`, `{path}`, `{slug}`. - Leave out `--since` to publish every entry. That is safe to repeat — unchanged entries are not rewritten. - `--dry-run` prints the batch body and does not need a token. Add `--json` for a machine-readable summary. Run `releases publish --help` for the full list. ## 4. Or POST the batch yourself ```bash curl --fail-with-body --silent --show-error \ --request POST \ --header "Authorization: Bearer ${RELEASES_API_TOKEN}" \ --header "Content-Type: application/json" \ --url "https://api.releases.sh/v1/sources/${RELEASES_SOURCE}/releases/batch" \ --data @- <<'JSON' { "mode": "upsert-content", "releases": [ { "title": "1.4.0", "content": "### Added\n- JSON export", "url": "https://example.com/changelog#1.4.0", "publishedAt": "2026-05-01T12:00:00Z", "version": "1.4.0", "type": "feature", "prerelease": false } ] } JSON ``` `mode` must be `"upsert-content"`. Omitted, the route only fills empty fields and will not update an entry you edited. A typo in `mode` is a 400. | Field | Required | Notes | | ------------- | -------- | ------------------------------------------------------------------------------------------------------ | | `title` | yes | Heading text. For a date section this is the date line (`June 10, 2026`). | | `content` | yes | Markdown body under that heading, or the flattened MDX body. | | `url` | yes | Stable permalink. This is the idempotency key together with the source. Do not put a commit SHA in it. | | `publishedAt` | no | ISO-8601. A date-only changelog heading is sent as noon UTC (`2026-05-01T12:00:00Z`). | | `version` | no | Semver or tag, when the entry has one. | | `type` | no | `"feature"` for a versioned entry, `"rollup"` for a date section. | | `prerelease` | no | `true` for alpha/beta/rc versions. | A successful response is `{ "inserted": , "total": , "insertedIds": ["rel_…"] }`. `inserted` counts new rows. An unchanged re-POST still returns 200 with `inserted: 0`. Send only the entries that changed. The Action and `releases publish` do that by diffing since the previous commit (`before-sha` / `--since`). The HTTP route does not look at git — it upserts the array you send. ## GitLab CI Variables: `RELEASES_API_TOKEN` (masked) and `RELEASES_SOURCE` (`src_…`). The job below installs the CLI and publishes what changed in the push. GitLab's checkout is shallow by default, so set `GIT_DEPTH: 0` for `--since` to find the previous commit: ```yaml publish-changelog: stage: deploy image: node:22 variables: GIT_DEPTH: 0 rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH changes: - CHANGELOG.md script: - npm install -g @buildinternet/releases - releases publish --source "$RELEASES_SOURCE" --since "$CI_COMMIT_BEFORE_SHA" ``` Without the CLI, `curl` posts the body directly. This job posts one entry. Swap the JSON for your own batch, or for the output of `releases publish --dry-run`: ```yaml publish-changelog: stage: deploy image: curlimages/curl:8.11.1 rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH changes: - CHANGELOG.md script: - > curl --fail-with-body --silent --show-error --request POST --header "Authorization: Bearer ${RELEASES_API_TOKEN}" --header "Content-Type: application/json" --url "https://api.releases.sh/v1/sources/${RELEASES_SOURCE}/releases/batch" --data '{"mode":"upsert-content","releases":[{"title":"1.4.0","content":"### Added\n- JSON export","url":"https://example.com/changelog#1.4.0","publishedAt":"2026-05-01T12:00:00Z","version":"1.4.0","type":"feature"}]}' ``` Both jobs work the same way on Buildkite, CircleCI, or a script step. Checkout depth does not matter for the HTTP call. It matters only for the client that diffs the changelog (the Action needs `fetch-depth: 0`; `releases publish --since` needs the commit you name to be present). ## GitHub Actions On GitHub, use the Action instead of hand-written JSON. It diffs `CHANGELOG.md` (or a `changelog-glob` of MDX files) and posts this body for you: ```yaml - uses: buildinternet/releases/actions/publish-changelog@main with: source: src_… api-token: ${{ secrets.RELEASES_API_TOKEN }} ``` Inputs, directory mode, and URL templates: [Publish from GitHub Actions](/docs/integrations/github-actions). ## What the server does with the batch The write is the same one ingest already uses. A new row gets content generation, embeddings, live events, and web revalidation. Deleting a file from the repo does not delete the release — that stays a curator action. --- # Browsing & Search Find organizations, sources, and releases in the index. ## Pair with agent skills One-line setup so your agent picks the right `releases` subcommand and flags on its own. See the [skills page](/docs/skills) for the full list. **Standalone (any agent):** ```bash npx skills add buildinternet/releases ``` Drops skill files into the project. Works in Claude Code, Codex, Cursor, OpenCode. **Claude Code plugin** (adds the bundled MCP server and `/releases` command): ```bash /plugin marketplace add buildinternet/releases /plugin install releases@releases ``` ## List sources The `list` command shows all configured changelog sources, or details for a single one. ```bash releases list # All sources releases list claude-code # Details for one source releases list --org vercel # Sources for an org releases list --has-feed # Only sources with a feed URL releases list --query "tailwind" # Search by name, slug, or URL releases list --category ai # Filter by category releases list --json # Machine-readable output releases list --json --compact # Lightweight JSON (id, slug, name, type, org, date) releases list --json --limit 20 # First 20 results as JSON releases list --json --limit 20 --page 2 # Paginated JSON releases list --json --page-all # Stream every page as NDJSON (one source per line) ``` Also available as `releases admin source list` for discoverability within admin workflows. ### Filters | Flag | Description | | -------------------- | -------------------------------------------------------------------------------------------------------- | | `--org ` | Filter by organization | | `--product ` | Filter by product | | `--has-feed` | Only sources with a discovered feed URL | | `--query ` | Substring match on name, slug, or URL | | `--category ` | Filter by org or product category | | `--include-disabled` | Include disabled sources | | `--compact` | Lightweight fields only (with `--json`) | | `--limit ` | Limit results (with `--json`) | | `--page ` | Page number (with `--limit`) | | `--page-all` | Stream every page as NDJSON (with `--json`; see [Stream every page](#stream-every-page-with---page-all)) | | `--fields ` | Project `--json` output to named fields (see [Project fields](#project-fields-with---fields)) | ## Latest releases The `tail` command shows the most recent releases and can optionally poll for new ones as they arrive. `latest` is retained as an alias for the one-shot listing. ```bash releases tail # Across all sources releases tail claude-code # From one source releases tail --org vercel --count 20 # Latest 20 from an org releases tail --since 30d # Only releases from the last 30 days releases tail --since 2026-01-01 --until 2026-03-31 # A specific window releases tail -f # Follow new releases (polls every 60s) releases tail -f --interval 30 # Follow with a 30s poll interval releases tail --json # Slim JSON output releases tail --json --full # Complete unprojected payload releases tail --json --fields id,version,source.slug # Project to just these fields ``` `--since` and `--until` limit results by publish date and combine with the other filters. Each accepts an ISO date (`2026-01-01`) or a relative shorthand (`90d`, `4w`, `6m`, `2y`). A value that can't be parsed exits with code `2`. The human view prints one aligned row per release: an identity (a package-qualified version like `@scope/pkg@1.2.3`, or the source name), a one-line description, a relative age, and a dimmed `rel_…` handle. `--json` returns a [slim release shape](#slim-release-json) by default; add `--full` for everything. ## Search Full-text search across organizations, products, sources, and releases. ```bash releases search "breaking change" releases search "authentication" --type releases --limit 5 releases search "vercel" --json releases search "vercel/next.js" # GitHub coordinate — falls back to on-demand lookup releases search "github:Shopify/toxiproxy" # Same coordinate with explicit provider prefix releases search "slack integration" --since 90d # Only release hits from the last 90 days ``` ### On-demand GitHub lookup When the query is a `{org}/{repo}` coordinate (optionally prefixed `github:`) **and** no entity (org or catalog source) matched, the CLI prints a `Lookup` rail above the regular results. The lookup fires even when loosely related release hits surfaced, because a coordinate is treated as a precise question about one repo. Org and repo segments match case-insensitively, so `shopify/toxiproxy` and `Shopify/Toxiproxy` resolve to the same source row. Possible statuses: | Status | Meaning | | ----------- | ------------------------------------------------------------------------------------- | | `INDEXED` | We just pulled this repo from GitHub on demand. Inline release preview follows. | | `EXISTING` | Repo was already tracked. Inline release preview follows. | | `EMPTY` | Real repo, but no tagged releases or CHANGELOG yet. | | `NOT_FOUND` | No public repo at `github.com/{org}/{repo}` (private, archived, renamed, or missing). | | `DEFERRED` | GitHub rate-limit or 5xx — try again in a moment. | If the org segment matches a known org but the specific repo doesn't, the CLI shows a "Did you mean" rail listing the top sibling sources for that org. The lookup also surfaces in `--json` output under the `lookup` key. ### Options | Flag | Description | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--type ` | Limit to one result type: `orgs`, `catalog`, `releases`, or `collections` | | `--limit ` | Max results per type (default 10) | | `--mode ` | Release-retrieval strategy: `lexical`, `semantic`, or `hybrid` (default) | | `--domain ` | Scope results to the org owning this domain (URL-shaped input is normalized) | | `--kind ` | Filter by taxonomy: `platform`, `sdk`, `mobile`, `desktop`, `docs`, `integration`, `tool`. For release hits the effective kind resolves from the source, falling back to its product (`COALESCE(source.kind, product.kind)`); catalog hits match the row's own `kind` only | | `--since ` | Keep only release hits published on/after this date (ISO or `90d`/`4w`/`6m`/`2y`) | | `--until ` | Keep only release hits published on/before this date (same formats as `--since`) | | `--json` | Machine-readable output ([slim release hits](#slim-release-json) by default) | | `--full` | With `--json`, return complete unprojected release hits | | `--fields ` | With `--json`, project each entity array down to named fields (see [Project fields](#project-fields-with---fields)) | `--mode`, `--domain`, and `--kind` shape retrieval and scoping; `--since` / `--until` filter the release hits only — org, product, and source matches are unaffected. In the human view, each release hit is one aligned row (`Org/Source` · title · relative age · dimmed `rel_…`) with a cleaned, markdown-stripped excerpt underneath — never raw `## heading`/`**bold**` markup. The identity leads with the owning org so cross-vendor results make clear who ships each release (the prefix is dropped when the source name already starts with the org name, e.g. `Railway Changelog`). ### Slim release JSON The release reader commands — `search`, `tail`/`latest`, and `get ` — return a **slim** release shape under `--json` by default, so agents aren't billed tokens for storage internals: ```jsonc { "id": "rel_…", "version": "@scope/pkg@1.2.3", "title": "…", "summary": null, // AI summary; often null "excerpt": "…", // markdown-stripped, ~280 chars "url": "https://…", "publishedAt": "2026-05-22T20:25:54.000Z", "source": { "slug": "…", "name": "…" }, "org": { "slug": "…", "name": "…" }, "contentChars": 51, "contentTokens": 24, } ``` Pass `--full` to recover the complete payload (`content`, `contentHash`, `composition`, `titleGenerated`/`titleShort`, and other internals). This is the inverse of `list`, which is verbose by default and opts _into_ a lightweight shape with `--compact`. ### Project fields with `--fields` When you only need a few leaves of the JSON, `--fields` post-filters the output down to a comma-separated mask. It works on the readers (`get` across release/source/org/product, `search`, and `tail`/`latest`): ```bash releases tail --json --fields id,version,source.slug releases get vercel --json --fields id,name,domain releases search "auth" --json --fields id,title,url ``` - Use **dot-notation** for nested keys (`source.slug`, `org.name`). It walks plain objects only — request an array-valued field like `media` whole. - It's a **post-projection** over whatever shape the reader produced, so it composes with `--full` (mask the full payload) and otherwise reuses the slim vocabulary — no new field names to learn. - A field that resolves to nothing is dropped with a single stderr warning (so a typo is visible without corrupting the JSON on stdout). `--fields` without `--json` warns and is ignored, the same as `--full`. ### Stream every page with `--page-all` The page-based list readers — `list`, `org list`, and `admin product list` — accept `--page-all`. Instead of returning one `{ items, pagination }` page that you have to walk with `--page`/`--limit`, the CLI walks every page itself and streams the result as newline-delimited JSON (NDJSON), one item per line: ```bash releases list --json --page-all | jq -c 'select(.type == "github")' releases org list --json --page-all ``` - NDJSON keeps memory flat and lets a consumer (`jq -c`, a stream parser) process rows as they arrive instead of buffering one giant array. - It's `--json`-only (without `--json` it warns and falls through to the table) and can't be combined with `--page`. `--limit` still sets the per-request page size as a round-trip tuning knob. ## JSON output, errors, and input validation Every reader command supports `--json`, and machine-readable output is stable enough to script against: - **Structured errors.** When a command run with `--json` fails, it prints a parseable error envelope to **stdout** (not an unstructured stderr dump) and exits non-zero, so you can branch on `kind` instead of string-matching: ```jsonc { "error": { "kind": "api", "message": "…", "status": 404, "method": "GET", "path": "/v1/..." } } ``` `kind` is `"api"` (carries `status`/`method`/`path`), `"invalid_input"` (carries the offending `field`), or `"error"`. Without `--json`, the two known error kinds print a clean one-line stderr message instead of a stack trace. - **List envelopes.** `--json` list responses return `{ items, pagination }`; `pagination` carries `{ page, pageSize, returned, hasMore }` (plus `totalItems`/`totalPages` once the tail is seen). When a default call fills a page and more rows exist, a stderr truncation warning fires so scripts don't silently miss rows — raise `--limit`, walk `--page`, or stream everything with `--page-all`. - **Input validation.** Identifiers are validated before any network call: control characters, whitespace, `..` traversal, `%` percent-encoding, and embedded `?`/`#` are rejected with an `invalid_input` error. This keeps a hallucinated or malformed identifier from being sent to the API. ## Categories Organizations and products are tagged with a category. List valid values with: ```bash releases categories ``` Each category accepts a configurable byline (display name + description) and optional aliases that redirect to the canonical slug. For example, `releases.sh/categories/e-commerce` 301s to `/categories/commerce`. Alias resolution is also applied on write paths, so `--category e-commerce` resolves to `commerce` before it lands in the database. ## Stats Get a quick count of organizations, sources, releases, and products in the database: ```bash releases stats ``` ## Feedback Send feedback about the CLI (a bug, an idea, or anything else) straight to the maintainers. No API key or account required. ```bash releases feedback "tail -f reconnects slowly on flaky wifi" # one-shot message releases feedback --type bug # prompts for the text (interactive) echo "longer write-up…" | releases feedback # pipe from stdin releases feedback "love the tool" --contact you@example.com # optional reply-to releases feedback "draft" --dry-run --json # preview the payload, send nothing ``` With no message argument in an interactive terminal, `feedback` prompts for the text (and an optional contact); otherwise pass it inline or pipe via stdin. `--type` accepts `bug`, `idea`, or `other`. Submissions are confirmed with an ID you can reference later. --- # REST API Query the Release Notes Index over plain HTTP. This page covers the conventions that apply to every endpoint. The per-endpoint reference is generated from the OpenAPI spec. - **Interactive reference:** [`api.releases.sh/v1/docs`](https://api.releases.sh/v1/docs) — full Scalar reference with request/response shapes, examples, and client snippets. - **OpenAPI 3.1 spec:** [`api.releases.sh/v1/openapi.json`](https://api.releases.sh/v1/openapi.json) — source of truth for request and response shapes. - **Base URL:** `https://api.releases.sh/v1` - **Errors:** [Errors](/docs/api/errors) — the standardized error envelope, types, and common codes. - **Webhooks:** [Webhooks](/docs/api/webhooks) — self-serve outbound `release.created` delivery (org-scoped or follows-filtered). ## Authentication Most read endpoints are public. No credentials required. **Operator and write endpoints** require a Bearer token with sufficient scope: ```bash curl -H "Authorization: Bearer YOUR_KEY" https://api.releases.sh/v1/... ``` **Account endpoints** (`/v1/me/*` — follows, feed, webhooks, API keys) require a signed-in user. Any of these work: - **Browser session** — sign in on [releases.sh](https://releases.sh) and call the API with credentials included (cookie on same-site requests). - **User API key** — minted via `releases login` or **Account → API Keys**. Pass as `Authorization: Bearer `. - **Sign in with Releases** — an OAuth access token from an app you've authorized. Pass as `Authorization: Bearer `. User credentials authenticate your account, not operator/admin access. Machine and operator tokens use a separate scope ladder. ## Pagination Different kinds of endpoints paginate differently. There are three shapes: | Shape | Surfaces | Input | Output | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | **Page-based** | Catalog-shaped: `/v1/sources`, `/v1/orgs`, `/v1/products`, `/v1/collections` | `page` + `limit` (default and max `500` unless noted) | `{ items, pagination }`, where `pagination: { page, pageSize, returned, totalItems, totalPages, hasMore }` | | **Cursor-based** | Feed-shaped: `/v1/orgs/:slug/releases`, `/v1/orgs/:orgSlug/sources/:sourceSlug` (and its embedded `releases` array), `/v1/orgs/:orgSlug/sources/:sourceSlug/releases`, `/v1/collections/:slug/releases`, `/v1/categories/:slug/releases` | opaque `cursor` | `pagination: { nextCursor, limit }`; `nextCursor` is `null` when the slice is exhausted | | **Search** | `/v1/search`, `/v1/search/releases` | query + `limit` | `_meta.search`, with `hitCap: true` when results saturated `limit` | ## Release date filtering `/v1/search`, `/v1/releases/latest`, and the org feed (`/v1/orgs/:slug/releases`) accept optional `since` and `until` query params that limit results by publish date. Each takes an ISO date/datetime (`2026-01-01`) or a relative shorthand (`90d`, `4w`, `6m`, `2y`). Both ends are inclusive: a bare-date `until` covers that whole UTC day, so `since=2026-06-25&until=2026-06-25` returns everything published on June 25. A value that can't be parsed, or an impossible date like `2026-02-30`, returns a `400`. On `/v1/search` these params filter the release hits only; the orgs, catalog, and collections sections are unaffected. Releases with no `published_at` are dropped from the window. ```bash curl "https://api.releases.sh/v1/search?q=slack%20integration&since=90d" curl "https://api.releases.sh/v1/releases/latest?org=vercel&since=2026-01-01&until=2026-03-31" curl "https://api.releases.sh/v1/orgs/vercel/releases?since=2026-06-25&until=2026-06-25" ``` ## Resource shape | Resource | Notes | | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Orgs** | Publish releases. Resolved by typed ID (`org_…`), slug, or a bare domain (primary or alias). | | **Products** | Optional grouping layer between orgs and sources. | | **Sources** | Changelog endpoints owned by an org. `type` is one of `github`, `scrape`, `feed`, `agent`. Resolved by typed ID (`src_…`) on the bare path, or by slug under the org-scoped path (`/v1/orgs/:orgSlug/sources/:sourceSlug`). | | **Releases** | Carry `id`, `orgId`, `sourceId`, `title`, `version`, `publishedAt`, `url`, `description`, `media`, plus optional `summary`, `title_generated`, `title_short`. | IDs are immutable; prefer them over slugs. ## Lookups When you only have a coordinate or a domain: | Endpoint | Resolves | | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | | `GET /v1/lookups/by-domain?domain=…` | A normalized domain to its owning org and any products whose alias targets the same domain. | | `GET /v1/lookups/source-by-slug?slug=…` | The canonical org-scoped home for a bare source slug. | | `GET /v1/lookups/product-by-slug?slug=…` | The canonical org-scoped home for a bare product slug. | | `POST /v1/lookups { provider: "github", coordinate: "org/repo" }` | Materializes a hidden source row from a GitHub coordinate on first call. | ## Upgrade intelligence (Beta) > **Beta — subject to change.** The shape and parameters of this endpoint may evolve. `GET /v1/whats-changed?package=…&from=…&to=…&ecosystem=npm|pypi|github` returns the changelog entries between two versions of a package: the releases in the range `(from, to]` (`from` exclusive, `to` inclusive), each with its summary, breaking-change verdict (`none` / `minor` / `major` / `unknown`), and any migration notes. Planning an upgrade becomes one call instead of reading many changelog pages. - `package` — a tracked source slug or a GitHub `owner/repo` coordinate (pass `ecosystem=github` for a bare coordinate). - Reads already-indexed releases only — no live fetch. - An untracked package returns `{ "status": "unknown" }` with **HTTP 200** (a valid answer, not a 404). npm/PyPI names that aren't yet mapped to a tracked source resolve to `unknown`. - Wide ranges are truncated to a token budget (newest entries kept), flagged by `truncated`. Also available as the `whats_changed` [MCP tool](/docs/api/mcp). ## Discoverability - The API is advertised by RFC 9727 at [`/.well-known/api-catalog`](/.well-known/api-catalog). - The OpenAPI 3.1 spec is the source of truth: [`https://api.releases.sh/v1/openapi.json`](https://api.releases.sh/v1/openapi.json). - Every org and source page on Releases Index has machine-readable URL suffixes: `.json` (programmatic), `.md` (LLM-friendly), `.atom` (feed readers). ``` https://releases.sh/anthropic # HTML https://releases.sh/anthropic.json # JSON https://releases.sh/anthropic.md # Markdown https://releases.sh/anthropic.atom # Atom 1.0 feed ``` Legacy bare paths (`/source/{slug}.atom`) 308-redirect to the canonical org-scoped form. --- # Errors Every non-2xx response uses one JSON shape: ```json { "error": { "code": "not_found", "type": "not_found", "message": "Organization not found", "details": {} } } ``` - **`type`** — a coarse category from a fixed set. It determines the HTTP status, so it's the safe field to switch on. - **`code`** — a more specific, stable machine string. Never reworded once shipped, but open-ended: treat an unrecognized code by falling back to its `type` (or the HTTP status). - **`message`** — human-readable, may change between releases. Don't parse it. - **`details`** — optional structured context carried by a few codes (e.g. setup steps for `database_not_initialized`). Absent otherwise. The HTTP status is authoritative and always agrees with `type`. ## Error types | `type` | HTTP | Meaning | | -------------------- | ---- | ------------------------------------------------------------- | | `validation` | 400 | The request was malformed or failed a field/shape check. | | `unauthorized` | 401 | Missing or invalid credentials. | | `forbidden` | 403 | Authenticated, but not allowed. | | `insufficient_scope` | 403 | The token is valid but lacks the required scope. | | `not_found` | 404 | No matching resource. | | `conflict` | 409 | The request conflicts with existing state (e.g. a duplicate). | | `rate_limited` | 429 | Too many requests — retry after a short wait. | | `upstream` | 502 | A dependency the API called failed. | | `unavailable` | 503 | The endpoint is temporarily unavailable. | | `internal` | 500 | An unexpected server error. | ## Common codes `code` is more specific than `type`. The ones you'll see most often: | `code` | `type` | When | | --------------------- | -------------------- | ---------------------------------------------- | | `validation_failed` | `validation` | A request body failed schema validation. | | `bad_request` | `validation` | A business rule rejected the request. | | `invalid_json` | `validation` | The body wasn't valid JSON. | | `payload_too_large` | `validation` | The request body exceeded the size cap. | | `unauthorized` | `unauthorized` | Sign-in or a valid token is required. | | `forbidden` | `forbidden` | The caller isn't permitted to do this. | | `insufficient_scope` | `insufficient_scope` | The token lacks the scope this endpoint needs. | | `not_found` | `not_found` | The resource doesn't exist. | | `conflict` | `conflict` | A uniqueness or state conflict. | | `rate_limited` | `rate_limited` | The rate limit was hit; honor `Retry-After`. | | `service_unavailable` | `unavailable` | The endpoint is disabled or temporarily down. | This isn't the full set — new codes can appear over time, so branch on `type` (or the HTTP status) and treat `code` as an optional refinement. The exhaustive list lives in the [OpenAPI spec](https://api.releases.sh/v1/openapi.json). ## Related - [REST API](/docs/api/rest) — conventions, authentication, pagination. - [Interactive reference](https://api.releases.sh/v1/docs) — every endpoint's request/response shapes. --- # Webhooks Outbound HTTPS notifications when new releases are indexed. Each delivery is a signed `POST` with a `release.created` payload. - **Public contract:** [docs/webhooks.md](https://github.com/buildinternet/releases/blob/main/docs/webhooks.md) in the monorepo (signature verification, retries, replay). - **Interactive API reference:** [`api.releases.sh/v1/docs`](https://api.releases.sh/v1/docs) — search for `/v1/me/webhooks`. - **Related:** [REST API](/docs/api/rest) · [MCP Server](/docs/api/mcp) ## Self-serve (`/v1/me/webhooks`) Requires a signed-in account: a browser session cookie, a user API key (from `releases login` or Account → API Keys), or an OAuth access token from Sign in with Releases. Same authentication as [follows and your personalized feed](/docs/api/rest#authentication). ### Org-scoped (default) Create a subscription for one organization, optionally narrowed with ANDed filters (`sourceSlug`, `productSlug`, `releaseType`): ```bash curl -X POST https://api.releases.sh/v1/me/webhooks \ -H "Authorization: Bearer $RELEASES_TOKEN" \ -H "Content-Type: application/json" \ -d '{"orgSlug":"vercel","url":"https://hooks.example.com/releases"}' ``` The response includes a **signing key once** at creation (and again after `rotate-secret`). Store it; you can't retrieve it later. Up to **10** org-scoped subscriptions per account. ### Follows-scoped One webhook URL for everything you follow, with the same matching rules as `GET /v1/me/feed`: - Following an **org** → all releases from that org's sources. - Following a **product** → releases from sources tied to that product. ```bash curl -X POST https://api.releases.sh/v1/me/webhooks \ -H "Authorization: Bearer $RELEASES_TOKEN" \ -H "Content-Type: application/json" \ -d '{"scope":"follows","url":"https://hooks.example.com/my-follows"}' ``` **One** follows-scoped subscription per account (separate from the 10 org-scoped cap). Optional `releaseType` (`feature` | `rollup`) narrows follows delivery. When you follow or unfollow something, the change applies on the next event. No webhook edits needed. ### Management endpoints | Method | Path | Purpose | | -------- | ----------------------------------- | ---------------------------------------------------------------------- | | `GET` | `/v1/me/webhooks` | List your subscriptions (includes delivery health) | | `GET` | `/v1/me/webhooks/:id` | Detail | | `PATCH` | `/v1/me/webhooks/:id` | Update URL, description, `enabled`, or filter fields | | `DELETE` | `/v1/me/webhooks/:id` | Remove | | `POST` | `/v1/me/webhooks/:id/rotate-secret` | Rotate HMAC signing key | | `POST` | `/v1/me/webhooks/:id/test` | Enqueue a synthetic test delivery | | `GET` | `/v1/me/webhooks/:id/deliveries` | Recent delivery attempts (see [Delivery activity](#delivery-activity)) | ### Finding your workspace id (`/v1/me/workspaces`) A workspace is your team's account on Releases Index. It's separate from the companies and products that webhooks watch. Every account has at least one personal workspace. `GET /v1/me/workspaces` lists the workspaces you belong to, so scripts, the CLI, and the MCP server can find a workspace id: ```bash curl https://api.releases.sh/v1/me/workspaces \ -H "Authorization: Bearer $RELEASES_TOKEN" ``` ```json { "workspaces": [ { "id": "Xk3vQ9dLm2PzR8tYw1Hn", "name": "Acme Team", "slug": "acme-team", "logo": null, "role": "owner", "active": true, "createdAt": "2026-01-01T00:00:00.000Z" } ] } ``` `role` is your role in that workspace (`owner`, `admin`, or `member`). `active` marks the workspace you last switched to. This route only reads. Create, rename, and switch workspaces from [your account settings](https://releases.sh/account/workspaces). ### Account UI Signed-in users can manage webhooks without raw API calls: **Account → Webhooks & API** on [releases.sh](https://releases.sh/account/webhooks). The Webhooks card supports list/create (follows or org), optional filters (`productSlug`, `sourceSlug`, `releaseType`), test delivery, pause/resume, rotate signing key, and delete. The signing key is shown once at create and rotate. From an organization page, **Add webhook** in the header ⋯ menu opens this form already scoped to that org. Each subscription shows **aggregate delivery health** (healthy / degraded / failing / paused). Expand **Activity** to see the last 15 per-attempt rows (time, outcome, HTTP status, latency, event id, error snippet). ### Delivery activity Every delivery attempt is recorded in a delivery log retained for ~**90 days**. Use it to see why a test or real event failed without tailing your own logs. ```bash # After releases login releases webhook test sleep 25 # delivery-log indexing lag — see below releases webhook deliveries --limit 10 ``` `GET /v1/me/webhooks/:id/deliveries` returns the same data. Optional query params: `limit` (1–100), `failed=true` (failures and retries only). Each row includes: | Field | Meaning | | --------------- | --------------------------------------------------------------------- | | `timestamp` | When the attempt was made | | `event_id` | `X-Releases-Event-Id` / release event id | | `outcome` | `success`, `retry`, `perm_fail`, `dlq`, `auto_disabled`, or `skipped` | | `http_status` | Subscriber response code (0 on network error) | | `latency_ms` | Round-trip time | | `attempt` | Retry attempt number (1 = first try) | | `error_message` | Truncated response body or error text on failure | **Indexing lag:** rows typically appear **20–30 seconds** after `POST …/test` or a real delivery. `releases webhook show` may say "No delivery attempts recorded" if you check immediately — wait a moment, then run `deliveries` again or reopen Activity in the account UI. List/detail endpoints still expose `deliveryHealth` and `consecutiveFailures` for at-a-glance status; the activity log is the per-event drill-down. ### URL safety and test limits Webhook URLs must be public **HTTPS** endpoints. Private IPs, internal hostnames, and metadata addresses are rejected at registration. `POST …/test` is rate-limited to **5/min per subscription** and **20/min per account** to prevent abuse. ### CLI After `releases login`, use `releases webhook list|add|show|edit|remove|test|rotate-secret|deliveries`. Filter flags on `add` / `edit`: `--product`, `--source`, `--type` (`feature` | `rollup`), and `--clear-*` on edit. See the [releases-cli skill](https://github.com/buildinternet/releases/tree/main/plugins/claude/releases/skills/releases-cli) for examples. `releases webhook verify` checks a captured payload locally (no auth). ## Workspace webhooks (`/v1/workspaces/:workspaceId/webhooks`) A webhook can also be owned by a workspace (a Workspaces organization, not a registry org) instead of your personal account, so a shared team channel keeps working after the person who set it up leaves. The route family mirrors `/v1/me/webhooks` exactly, one level down under the workspace: | Method | Path | Purpose | | -------- | -------------------------------------------------------- | ------------------------------------------- | | `GET` | `/v1/workspaces/:workspaceId/webhooks` | List — includes your `role` and `canManage` | | `POST` | `/v1/workspaces/:workspaceId/webhooks` | Create (owner/admin only) | | `GET` | `/v1/workspaces/:workspaceId/webhooks/:id` | Detail | | `PATCH` | `/v1/workspaces/:workspaceId/webhooks/:id` | Update (owner/admin only) | | `DELETE` | `/v1/workspaces/:workspaceId/webhooks/:id` | Remove (owner/admin only) | | `POST` | `/v1/workspaces/:workspaceId/webhooks/:id/rotate-secret` | Rotate HMAC signing key (owner/admin only) | | `POST` | `/v1/workspaces/:workspaceId/webhooks/:id/test` | Enqueue a synthetic test delivery | | `GET` | `/v1/workspaces/:workspaceId/webhooks/:id/deliveries` | Recent delivery attempts | Differences from a personal webhook: - **Org-scoped only** — there's no workspace "follows" scope. `POST` with `{"scope": "follows"}` returns `400`. - **Owners and admins** create/edit/rotate/delete; **any member** can list, view, and test. A non-member gets `404`; a member without manage rights gets `403`. - Up to **10** subscriptions per workspace, tracked separately from each member's personal cap. - Deleting the workspace deletes its webhooks; a member leaving does not. ## Admin-provisioned webhooks Operators with admin API access can manage org-scoped subscriptions via `POST /v1/webhooks` (admin route family). See the admin CLI docs for `releases admin webhook …` commands. ## Slack delivery New to this? The [Send releases to Slack](/docs/integrations/slack) guide walks through it step by step. The reference below covers the API/CLI details. Set `format: "slack"` (or `--format slack` on the CLI) and point the subscription at a [Slack incoming webhook](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/) URL (`https://hooks.slack.com/services/...`). Each release is posted as a compact Slack message — a linked title, a short summary, and a context line with the organization's avatar and date. Slack webhooks are **unsigned**: the URL itself is the secret, so no signing key is issued and no `X-Releases-*` signature headers are sent. There is nothing to verify on the Slack side. Use the **Test** button (or `releases webhook test `) to post a sample card. The host must be `hooks.slack.com` (standard and Enterprise Grid) or `hooks.slack-gov.com` (GovSlack); other hosts are rejected at creation. ## Discord delivery New to this? The [Send releases to Discord](/docs/integrations/discord) guide walks through it step by step. The reference below covers the API/CLI details. Set `format: "discord"` (or `--format discord` on the CLI) and point the subscription at a [Discord incoming webhook](https://support.discord.com/hc/en-us/articles/228383668) URL (`https://discord.com/api/webhooks/...`). Each release is posted as a compact Discord embed — a linked title, a short summary, and the organization's avatar and date. Discord webhooks are **unsigned**: the URL itself is the secret, so no signing key is issued and no `X-Releases-*` signature headers are sent. There is nothing to verify on the Discord side. Use the **Send test** button (or `releases webhook test `) to post a sample embed. The host must be `discord.com` (or `discordapp.com`, `canary.discord.com`, `ptb.discord.com`) and the path must be `/api/webhooks/{id}/{token}`; other hosts and the Slack-compat `/slack` suffix are rejected at creation. --- # MCP Server Use Release Notes Index as an AI agent tool server via the Model Context Protocol. ## Pair with agent skills One-line setup so your agent reaches for these tools on its own. See the [skills page](/docs/skills) for the full list. **Standalone (any agent):** ```bash npx skills add buildinternet/releases ``` Drops skill files into the project. Works in Claude Code, Codex, Cursor, OpenCode. **Claude Code plugin** (adds the bundled MCP server and `/releases` command): ```bash /plugin marketplace add buildinternet/releases /plugin install releases@releases ``` ## Remote server (recommended) Connect to the hosted MCP server at `https://agents.releases.sh/mcp`. No installation or API keys required — all tools are read-only and public. `https://mcp.releases.sh/mcp` continues to work as an alias. ## Setup instructions ### General The hosted MCP server supports Streamable HTTP at: ```text https://agents.releases.sh/mcp ``` Use that URL directly in clients with native remote MCP support. For clients that only support stdio MCP servers, use `mcp-remote` as a compatibility bridge. ### One-click install Click to install in a supported editor. The deeplink opens the app and prompts you to confirm before adding the server. ### Claude Release Notes Index is listed in the [Claude connector directory](https://claude.ai/directory/releases-index). Open the listing to connect it to Claude on the web, desktop, and mobile. No API key is needed. ### Claude Code ```bash claude mcp add --transport http releases https://agents.releases.sh/mcp ``` ### Codex ```bash codex mcp add releases --url https://agents.releases.sh/mcp ``` ### VS Code, Windsurf, Zed, and others For clients without native remote MCP support, use `mcp-remote`: ```json { "mcpServers": { "releases": { "command": "npx", "args": ["-y", "mcp-remote", "https://agents.releases.sh/mcp"] } } } ``` ## Available tools > **Identifiers:** every tool that takes an org / product / source identifier accepts the typed ID (`org_…`, `prod_…`, `src_…`) interchangeably with the slug. Source and product params also accept an `org/slug` coordinate (e.g. `vercel/nextjs`). Releases are addressed by id only — `get_release` takes a `rel_…` id or a bare 21-char nanoid, with no slug form. Each tool's `inputSchema.description` lists the concrete shapes it accepts. ### Read tools Public — no authentication needed on the remote server. “Server” says where each tool lives: **remote** is the hosted server at `agents.releases.sh`; **local** is `releases admin mcp serve`; **both** means the same tool name exists on each, though the local version is a smaller proxy (see each row for what it drops). | Tool | Server | Description | | ------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `search` | both | Unified search across orgs, catalog (products + standalone sources), curated collections (cross-org playlists), and release content. Accepts `type: ("orgs" \| "catalog" \| "releases" \| "collections")[]` to skip sections; `mode: "lexical" \| "semantic" \| "hybrid"` (default `hybrid`) for release retrieval; `entity` to scope releases; `since` / `until` to bound release hits by publish date (ISO or `90d`/`4w`/`6m`/`2y`). Collection hits surface via a direct name/description match or a member rollup (carrying the result-set org slugs that triggered it). Release hits carry a `kind: "release" \| "changelog_chunk"` discriminator; chunk hits include `chunkOffset` and `chunkLength` so you can chain into `get_catalog_entry` with changelog slicing params for surrounding context. The local server's version is the same idea, proxied through the REST API, but its `type` filter only covers `orgs`/`catalog`/`releases` (no `collections`), and it has no `domain`/`entity`/`kind`/`since`/`until`/`minImportance` params. | | `list_catalog` | both | Products and standalone sources folded into one list, each row tagged with an `entryType: "product" \| "source"` discriminator. | | `get_catalog_entry` | both | Detail for a single catalog entry — product or source. Accepts slug or `prod_` / `src_` id. Source entries list tracked CHANGELOG files (path + byte size) by default. Pass `include_changelog: true` to inline the root CHANGELOG, or `changelog_path` / `changelog_offset` / `changelog_limit` / `changelog_tokens` to target a specific file or slice. Heading-aligned slicing supports monorepo per-package files; token-mode responses include `totalTokens` and `sliceTokens` for LLM budgeting (brackets: 2000/5000/10000/20000). Both servers support this the same way. | | `get_latest_releases` | both | Get the most recent releases, optionally filtered by product, organization, release `type`, source/product `kind`, a `since` / `until` publish-date window (ISO or `90d`/`4w`/`6m`/`2y`), or a `minImportance` (1–5 AI-scored) floor. Excludes prereleases by default. Cursor-paginated (`limit`, `cursor`). The local server's version only takes `product` and `organization`. | | `list_organizations` | both | List all organizations, searchable by name, slug, domain, or account handle. Orgs with zero indexed releases are hidden by default; the local server adds `include_empty: true` to show them. | | `get_organization` | both | Detailed view of a single organization including accounts, tags, sources, products, and domain aliases. On the remote server this shows a preview of the AI-generated overview by default; pass `include_overview: true` to inline the full briefing (with a stale warning if it's older than 30 days). The local server always renders a plain summary and has no overview option. | | `lookup_domain` | remote | Resolve a URL-shaped domain to the org or product that owns it. Normalizes scheme/`www.`/path first, so `https://vercel.com/about` and `vercel.com` match the same row. Pure lookup — never probes or materializes anything. | | `list_collections` | remote | List curated collections — named cross-org playlists (e.g. "Frontier AI Labs") independent of the category taxonomy. Paginated, 50 per page. | | `get_collection` | remote | Detail for one collection — name, description, and its ordered member organizations. Hidden/on-demand orgs never appear in the list. | | `get_collection_releases` | remote | Interleaved cross-org release feed for a collection — same shape as `get_latest_releases`, scoped to the collection's member orgs. Cursor-paginated. | | `get_release` | remote | Fetch the full content of a single release by id. Accepts a `rel_` prefix or a bare nanoid. | | `whats_changed` | remote | **Beta — subject to change.** Changelog entries between two versions of a package: `whats_changed(package, from, to, ecosystem?)` returns the releases in the range `(from, to]` (`from` exclusive, `to` inclusive) with summaries, breaking-change verdicts, and migration notes — one call instead of reading many changelog pages to plan an upgrade. `package` is a tracked source slug or a GitHub `owner/repo` coordinate (set `ecosystem: "github"` for a bare coordinate). An untracked package returns a clear "not tracked" answer (npm/PyPI names aren't all mapped to a source yet). Public — no sign-in needed, despite living next to the account tools below. | | `get_source` | local | Local-only. Detail for a single changelog source by slug or id — a narrower, source-only precursor to `get_catalog_entry`. | | `changelog` | local | Local-only. Reads Releases Index's own product changelog (platform and CLI updates) — unrelated to any indexed org's releases. Same content as `releases changelog`. | ### Your account tools These act on **your own account** — following, your personalized feed, and your outbound webhooks — not the shared registry. Remote server only (`agents.releases.sh`); the local server doesn't have them. They need a signed-in identity: a user API key (`relu_…`, from `releases login` or Account → API Keys) or a "Sign in with Releases" OAuth access token, sent as a Bearer credential. An anonymous, machine (`relk_…`), or root credential gets a clear error instead of a call — there's no catalog `write` scope requirement, so a read-only `relu_` key can still manage its own follows and webhooks. | Tool | Description | | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `follow` | Follow an org or product so it appears in `get_personalized_feed`. Following an org implicitly includes all of its products. Takes an `org_…` / `prod_…` id (from `search` or `get_*` results). Idempotent. | | `unfollow` | Stop following an org or product. Same id shape as `follow`. Idempotent. | | `list_follows` | List the orgs and products you follow, newest first. | | `get_personalized_feed` | Your personalized release feed — recent releases from everything you follow, newest first. Same item shape as `get_latest_releases`. Cursor-paginated. | | `list_webhooks` | List your outbound `release.created` webhooks. Pass an optional `workspace` (id or slug) to list a workspace's webhooks instead of your personal ones; omitting it also appends a short list of your workspaces so you can find an id to pass next. Pass `id` to see one webhook plus its 10 most recent deliveries. | | `manage_webhook` | Create, update, delete, test, or rotate the signing key of a webhook, personal or workspace, behind one `action` enum (`create` / `update` / `delete` / `test` / `rotate_secret`). `create` needs `url` and `format` (`json`, `slack`, or `discord`). | **Webhooks in more detail:** - **Personal vs. workspace:** omit `workspace` for your own webhooks (`scope: "follows"` — everything you follow — or org-scoped). Pass a workspace id or slug to manage that workspace's webhooks instead; workspace webhooks are always org-scoped (`scope: "follows"` is personal-only and is rejected if you pass `workspace` with it). - **Who can do what in a workspace:** owners and admins can create, update, delete, and rotate a workspace's webhooks. Any member can list them and run `test` on one, even without manage rights. - **`format` is required on `create`** — pick `json`, `slack`, or `discord` up front; there's no default. - **Signing keys are shown once.** A `json`-format webhook's signing key only ever appears in the `create` or `rotate_secret` response — save it immediately, since it isn't shown again. - **Slack and Discord aren't signed.** Those two formats skip HMAC signing entirely, so `manage_webhook` never returns a signing key for them. See the [Webhooks API docs](/docs/api/webhooks) for the underlying REST contract, retry behavior, and payload shape. ### On-demand GitHub lookup `search` falls back to an on-demand GitHub lookup when the query is a `{org}/{repo}` coordinate and the in-index search returns no hits. The result is merged under a `lookup` field in the tool response so the agent can see the repo state without a second tool call. | Field | Description | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `status` | `indexed` (newly materialized), `existing` (already tracked), `empty` (real repo, no releases or CHANGELOG yet), `not_found` (no public repo), or `deferred` (GitHub rate-limit or 5xx — try shortly). | | `source` | Source record for the materialized or existing repo. Present on `indexed`, `existing`, and `empty`. | | `releases` | Inline release preview. Present on `indexed` and `existing`. | | `relatedOrg` | "Did you mean" rail — set when the org segment matches a known org but the specific repo doesn't. Lists the org and up to 5 sibling sources. | `lookup` is `null` when the query is not coordinate-shaped or when existing search hits were found. Materialized rows are hidden (`discovery: "on_demand"`); a second search for the same coordinate resolves through the normal cache path. Embeddings still run for on-demand sources, so semantic search picks them up on the second hit; AI features (overviews, summarization, playbook regen) skip them. ## Example usage Once configured, you can ask Claude to interact with the release index directly: - "What did Vercel ship last week?" - "Search for breaking changes in the Prisma changelog" - "Compare Next.js and Remix releases from the last 30 days" - "Summarize Cloudflare's recent releases, focusing on Workers" --- # How the index is built Release Notes Index collects release notes from the places companies already publish them. Each entry is stored as a release with a link back to its source, plus a few AI-generated fields. The original text and its source stay on every release. For the formats and interfaces you can use to read the data, see [Data](/docs/data). ## Adding sources A **source** is one place a company publishes updates: a GitHub repository's releases, an RSS or Atom feed, a changelog page, an App Store listing, or a YouTube channel. Sources belong to an **organization** and can be grouped under a **product**. The [overview](/docs) explains this hierarchy. Sources reach the index in four ways: - **Curated onboarding.** Maintainers add most sources by hand. AI agents help them find a company's changelog, feed, or GitHub repository and pick the most direct way to read it. A feed or the GitHub API is preferred over reading a rendered page. - **Owner listing.** A company can publish a [`releases.json`](/docs/listing) file on its domain that says where it posts updates. Checking the file on the [submit page](/submit) creates a basic listing for that domain. After the owner proves they control the domain, feed, GitHub, and App Store entries become live sources. Plain page URLs wait for a maintainer to review them. - **Suggestions.** Anyone can suggest a release notes URL on the [submit page](/submit). A maintainer reviews it before anything is added. - **On-demand lookups.** An authorized agent can ask for a GitHub repository the index does not have yet. The repository is added as a hidden source. It can answer that agent's question, but it does not appear in browsing and does not get AI summaries. ## Check schedule Each source is checked on its own schedule. Sources that publish often are checked several times a day, and sources that publish rarely about once a day. A daily job looks at how often each source has published recently and moves it between those two schedules. When a source has not changed for a while, or keeps returning errors, the wait between checks grows. When it changes again, checks speed back up. For feeds, the index first asks the server whether anything changed, and skips the full download when nothing did. Two kinds of source are never checked: - **Paused sources** are skipped until a maintainer turns them back on. - **Push-fed sources** get new entries from their publishers' own build pipelines (see [Publishing directly](#publishing-directly)), so they have no schedule. ## From page to release How a release is read depends on the source type: - **Feeds and GitHub releases** are already structured. Each item becomes a release without an AI step. - **Changelog pages** get a feed check first. If the page has an RSS, Atom, or JSON feed, the index uses it. If not, the page is loaded in a browser, and an AI model reads it and pulls out each entry's title, version, date, and text. Very long pages are read in pieces so the model handles only the parts it needs. - **Feeds that carry only a short summary** get a second step. For each new item, the index fetches the linked post and uses an AI model to pull out the article text. If that fails, the feed summary is kept. Images in a release are copied to the index's own media host, so they keep loading if the original image URL changes. Links to hosted videos (YouTube, Vimeo, Loom, Wistia) show as a preview card that links to the video. ### Duplicates A release is identified by its source and its URL. When a source is checked again, an entry that is already stored is not added a second time. A later check can fill in missing text, but it does not overwrite text that is already there. ### One launch, several posts Companies often announce one launch in several places: a blog post, a changelog entry, and an app update note. The index can group these so the launch shows once, with the other posts listed as coverage of it. When one change bumps versions across many packages in a monorepo, those releases are grouped automatically as they arrive. Maintainers make other groupings with help from an AI agent, and each grouping records who made it. Grouped posts are hidden from default lists but stay in the index. ### Excluded content - **Ignored and blocked URLs.** Maintainers can ignore specific URLs for one organization, or block spam domains everywhere. Matching entries are never stored. - **Suppressed releases.** A release can be hidden from every public list and search without being deleted. Maintainers do this by hand, and the marketing filter below does it automatically. ## AI-generated fields AI models write the parts below. Most requests go through OpenRouter and some go to Anthropic directly. The models only see public release content. The site labels these parts, and the original release text and its source link stay next to them. - **Titles and summaries.** A generated title, a short title, and a summary of the release. On a release page the summary sits above the original text, with a note that AI-generated summaries may contain mistakes. - **Importance score.** A score from 1 to 5 for how significant a release is for that company. The site marks releases that score 4 or 5. When the model cannot score a release, it has no score; the index never fills in a guess. - **Breaking-change check.** Whether a release appears to contain a breaking change. This feeds answers to "what changed between these two versions" questions. - **Organization overviews.** A short briefing on what a company has shipped recently. Overviews are rewritten on a schedule as new releases arrive, and every surface flags an overview that has gone stale. - **Collection summaries and weekly digests.** For [collections](/collections) of related companies, a short daily summary and a weekly digest of what shipped. Digests carry their own AI disclaimer. - **Marketing filter.** Some company blogs mix product news with case studies, newsletters, and event recaps. For those sources only, an AI model reads each new post and hides the ones it is confident are marketing. Hidden posts are kept, not deleted, and the decision can be reversed. Summaries, importance scores, and overviews are turned on per organization, so not every release has them. If an AI step fails, the release is stored as it came in, without the AI fields. ## Publisher controls ### Opt-out signals When maintainers use AI agents to read a site's pages directly, a required first step reads the site's `robots.txt` file (the file sites use to tell automated visitors what they may fetch). If the file has a Content Signal of `ai-input=no`, which asks that the content not be used as input to AI, the agent stops and does not add the content. A signal of `ai-train=no` on its own does not stop it, because the index does not train AI models. When the agent cannot read the file clearly, it stops and asks a person. The index's crawler identifies itself with its own user agent and signs its direct requests. The [crawler page](/bot) lists the details. ### Publishing directly Publishers whose changelog lives in a Git repository can send new entries straight to the index instead of waiting for the next check. After the domain is verified, the owner creates a publish token for one source and uses the [GitHub Action](/docs/integrations/github-actions) or [`releases publish` from any CI](/docs/integrations/ci). Each push adds or updates entries right away. The [listing guide](/docs/listing) covers the `releases.json` side. ### Corrections and removal - To report a wrong or broken release, organization, or source, use the **Report** link on that page. The report goes to the maintainers along with the page it came from. - To have a source removed, or to report content you believe infringes your rights, email [abuse@releases.sh](mailto:abuse@releases.sh) with the source or page URL and your relationship to the content. The [privacy policy](/privacy#takedowns-and-content-removal) describes the process. - For anything else, email [hi@releases.sh](mailto:hi@releases.sh). ## Attribution Every source page links to the publisher's own page. Every release that has its own URL has a "View original" link to the original post, and the release page's structured data marks it as based on that post. Copyright in release notes stays with the publishers; see the [terms](/terms#content-and-attribution). --- # Data Release Notes Index is a public registry of software releases. Most of it can be read without an account or API key: on the website, as Markdown, JSON, or Atom versions of each page, through the REST API, the MCP server, or the CLI. For how the data is collected and which fields are AI-generated, see [How the index is built](/docs/methodology). ## Contents of the index - **Organizations.** Companies and teams that publish releases, with a name, domain, category, and tags. - **Products.** An optional layer inside an organization, for companies that ship more than one product. - **Sources.** The places an organization publishes updates: GitHub releases, RSS and Atom feeds, changelog pages, App Store listings, and YouTube channels. For GitHub sources, the repository's `CHANGELOG` files are stored as well. - **Releases.** One entry each, with a title, version, publish date, the original text, images, and a link to the original post. Many releases also carry AI-generated fields: a title, a short title, a summary, and an importance score from 1 to 5. - **Collections.** Curated groups of related organizations, such as a set of AI labs or frontend frameworks. Each has a combined release feed, short daily summaries, and weekly digests. - **Categories.** A fixed list of industry groupings for organizations, each with its own release feed. - **Organization overviews.** AI-generated briefings on what each company has shipped recently. ## Reading the data ### Web pages Browse the [catalog](/catalog), [categories](/categories), [collections](/collections), and the [latest releases](/updates) on the site. Each organization, product, source, and release has its own page. ### Markdown, JSON, and Atom variants Add a suffix to a page URL to get the same content in another format. No account or key is needed. | Suffix | Format | Works on | | ------- | --------------------------------------- | ------------------------------------------------------------------------------------ | | `.md` | Markdown, for reading into an LLM | Organization, product, source, release, collection, category, digest, and docs pages | | `.json` | JSON | Organization, product, source, release, collection, and category pages | | `.atom` | Atom feed, for feed readers and scripts | Organization, product, source, collection, category, and digest pages | For example, `https://releases.sh/anthropic.md` and `https://releases.sh/anthropic/claude-code.atom`. A page URL also returns Markdown when the request sends an `Accept: text/markdown` header. The [RSS guide](/docs/guides/changelog-rss-feed) covers the Atom feeds in more depth. ### REST API The [REST API](/docs/api/rest) at `https://api.releases.sh/v1` serves organizations, products, sources, releases, collections, search, and more as JSON. Most read endpoints are public and need no key. Requests without a key are rate-limited per IP address. An [OpenAPI 3.1 spec](https://api.releases.sh/v1/openapi.json) describes every public endpoint. ### MCP server MCP (Model Context Protocol) is an open standard for connecting AI agents to tools and data. The hosted [MCP server](/docs/api/mcp) lets agents such as Claude and Cursor search the index and read releases as tool calls. The read tools are public and need no key. ### CLI The open-source `releases` [CLI](/docs/cli/browsing) lists, searches, and reads the index from a terminal. [Installation](/docs/installation) covers Homebrew, npm, and prebuilt binaries. `releases tail -f` follows new releases as they land. ### Notifications and webhooks With a free account you can follow organizations and products and get new releases pushed to you: an email digest, a [Slack](/docs/integrations/slack) or [Discord](/docs/integrations/discord) message, or a signed [webhook](/docs/api/webhooks) to your own server. The [notifications guide](/docs/guides/release-notifications) compares the options. ### llms.txt [`/llms.txt`](/llms.txt) is a short, machine-readable index of these docs and endpoints for AI agents. [`/llms-full.txt`](/llms-full.txt) holds every docs page in one file. ## Use and attribution Release notes belong to the companies that published them, and the index makes no ownership claim over that content. Each release links back to its source, as described in [Attribution](/docs/methodology#attribution). Before you republish a publisher's text, check that publisher's own terms. The [terms of service](/terms) cover acceptable use, including rate limits and bulk republishing. The CLI is open source under the MIT license. --- # Privacy & Telemetry Release Notes Index collects anonymous usage data from the CLI and the local MCP stdio server so we can understand which commands and tools are actually used. This page documents exactly what is collected, how to see it, and how to turn it off. For the full privacy policy covering the web app, API request logs, third-party processors, and data retention, see [/privacy](/privacy). ## What is collected Every CLI command and every local MCP tool call records a single event with: - **Command or tool name** — e.g. `search`, `list`, or `tool get_latest_releases` - **CLI version** — e.g. `0.10.0` - **Device info** — operating system, architecture, and how the CLI is running (e.g. `darwin arm64`, compiled binary or Bun) - **Outcome** — exit code and duration in milliseconds - **Anonymous ID** — a random UUID generated on first run and stored at `~/.releases/telemetry-id` - **Timestamp** — when the event occurred ## What is never collected - Command arguments or flag values - File paths, source slugs, org or product names, release IDs - Search queries, prompt text, or any other content you type - Usernames, email addresses, hostnames, IP addresses (beyond what any HTTP request exposes to the receiving server) - The contents of any release, changelog, or summary ## Anonymous ID The first time the CLI runs, a random UUID is written to `~/.releases/telemetry-id` with `0600` permissions. This ID is not tied to a user, account, or machine identifier. It's a random value that lets us count unique clients across events. Deleting the file on your end resets your identity. ## First-run notice On first run, the CLI prints a one-time notice to `stderr` summarizing what is collected and how to opt out. The notice is suppressed on subsequent runs, on CI, and on clients that identify as internal. ## Opting out You can disable telemetry at any time. Any of the following will silence it: ```bash releases telemetry disable # persistent opt-out (stored locally) RELEASES_TELEMETRY_DISABLED=1 releases … # per-invocation opt-out DO_NOT_TRACK=1 releases … # also respected ``` To see the current state, the anonymous ID, and the endpoint events would post to: ```bash releases telemetry status ``` To re-enable: ```bash releases telemetry enable ``` ## How events are sent Events are sent best-effort to `POST /v1/telemetry` on the public API with a 1.5-second timeout. If the request fails, times out, or the machine is offline, the event is silently dropped. Telemetry never blocks a command and never surfaces errors. ## Retention Telemetry events are retained for 90 days and then deleted. ## Questions For questions about telemetry or anything else data-related, email [privacy@releases.sh](mailto:privacy@releases.sh). For takedown requests, email [abuse@releases.sh](mailto:abuse@releases.sh). For security reports, email [security@releases.sh](mailto:security@releases.sh). The full privacy policy lives at [/privacy](/privacy).