The changelog document: JSON and Atom

Embed
Preview of the card
<a href="https://forumulate.com/guides/changelog-feeds"><img src="https://forumulate.com/guides/_api/public/orgs/046a9bc0-d530-43ae-9bae-af7945008cda/spaces/36aa4df0-1e95-4cef-8ae9-795169441345/pages/853644ac-27e1-481d-948b-ca6d38b4eeb9/social-card.svg?v=3e7ae33eec8b" alt="The changelog document: JSON and Atom" width="600" height="315"></a>

Every site you publish answers /changelog.json and /changelog.xml at its own address, and every Changelog space answers the same two files at its own path. They carry what your RSS feed carries and what a script wants besides: each release's version, its categories (added, changed, fixed, deprecated, removed, security), whether it is breaking, and its line items, as structured fields.

What

Address

The site's changelog

https://acme.com/changelog.json · https://acme.com/changelog.xml

A changelog space

https://acme.com/changelog/changelog.json · https://acme.com/changelog/changelog.xml (the space's own path)

On our address

/s/{orgId}/{siteSlug}/changelog.json · /c/{orgId}/{spaceId}/changelog.json

API

GET /public/orgs/{orgId}/sites/{siteSlug}/changelog.json · GET /public/orgs/{orgId}/spaces/{spaceId}/changelog/changelog.json (and .xml)

On forumulate.com itself, /changelog.json is the platform's own API changelog and is unchanged; a customer's file is never that one.

Only what everybody may read is in it: published releases (a draft never appears), not members-only ones, and not expired ones. A private changelog answers 404, and a site's root document is fed by a public changelog space only, so nothing private leaks through the site.

The format

JSON Feed 1.1 with the structured fields beside the spec's own. A feed reader that understands JSON Feed reads it as it is; everything else ignores the extra keys.

{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "Acme changelog",
  "description": "What shipped.",
  "home_page_url": "https://acme.com/changelog",
  "feed_url": "https://acme.com/changelog.json",
  "next_url": "https://acme.com/changelog.json?cursor=MTc3MzIy...",
  "_changelog": {
    "schema": "https://forumulate.com/schemas/changelog-document-v1.json",
    "total": 42,
    "next_cursor": "MTc3MzIy...",
    "updated": "2026-03-04T11:00:00.000Z"
  },
  "items": [
    {
      "id": "https://acme.com/changelog/v2-4-0",
      "url": "https://acme.com/changelog/v2-4-0",
      "title": "Faster exports",
      "summary": "Exports are three times faster.",
      "content_html": "<h3>Added</h3><ul><li>...</li></ul>",
      "content_text": "Added ...",
      "date_published": "2026-03-04T10:00:00.000Z",
      "date_modified": "2026-03-04T11:00:00.000Z",
      "tags": ["added", "breaking", "api"],
      "version": "2.4.0",
      "type": ["added", "fixed"],
      "breaking": true,
      "entries": [
        { "type": "added", "text": "CSV export streams instead of buffering." },
        { "type": "fixed", "text": "Export no longer drops the last row." }
      ]
    }
  ]
}
  • version (document) is the JSON Feed version. The release's own label is the item's version, or null.

  • type is the list of categories the release has: its declared type, and any tag that names one. Common words map across: new and feature are added, improved and update are changed, bugfix is fixed, retired is removed. A release whose type is none of the six has type: [] and keeps its own tags.

  • breaking is true when the release is marked breaking, or carries a tag in your breaking tags (default breaking, breaking-change). A breaking release also gets the breaking tag.

  • entries are the list items of the release body. Under a heading that names a category (### Fixed), an entry is of that category; elsewhere, of the release's single category, or none.

  • date_published and date_modified are RFC 3339 UTC. date_modified moves when the release's content is edited, not on a cosmetic change.

  • id and url are the release's own page (or the changelog page at the release's anchor, see Link style).

/changelog.xml is Atom 1.0 from the same data: each entry has <category term> for the tags, <content type="html">, and, in the https://forumulate.com/ns/changelog namespace, <cl:version>, <cl:breaking> and a <cl:entry type> per line item. One serializer writes both, so they cannot disagree.

Query parameters

Parameter

Meaning

since, until

Releases published on or after since, before until (ISO 8601).

type

Comma list of categories or tags; a release matching any is kept: ?type=fixed,security.

limit

Releases per response, up to your configured limit (default 50, at most 500).

cursor

The previous response's next_cursor. Newest first; the cursor neither repeats nor skips a release, even when releases share a timestamp.

format

changelog (default) or jsonfeed, which is strictly JSON Feed 1.1: the structured fields are dropped and the categories and breaking survive as tags.

Walk the whole history by following next_url until it is absent.

The response carries an ETag and Last-Modified; send If-None-Match and an unchanged changelog costs a 304. It is served with Access-Control-Allow-Origin: *, so a browser on any origin can fetch it, and cached for a minute at the edge, so a publish shows within that.

Configure it

The same options at both levels. A site's go in the site's config under changelogFeed; a space's go in its settings under changelogFeed. A key a site leaves out falls back to the changelog space that feeds it, then to the default.

Key

Meaning

enabled

false turns the document off (404). Default on.

spaceId

Site only: which changelog space feeds the site root. Default: the first public changelog space in the site's order. Must be a member of the site.

title, description

Override the document's title (default: the space's name) and description (default: the space's SEO description).

limit

Releases per response, 1 to 500. Default 50.

categories

Expose only releases of these categories. Default: all, including releases of no category.

breakingTags

Tags that mark a release breaking, besides its own flag. Default ["breaking", "breaking-change"]; [] leaves only the flag.

linkStyle

release (each item links to the release's page, default) or changelog (to the changelog page at the release's anchor).

Drafts are never exposed; that is not a setting.

In the studio: a changelog space's Feed document section, and the site's Changelog feed group, show the addresses with a copy button, every option above and a preview of the first releases.

Through the API:

# a space
curl -X PATCH https://forumulate.com/api/orgs/$ORG/spaces/$SPACE \
  -H "Authorization: Bearer $TOKEN" -H "Forumulate-Version: 2027-03-01" -H "content-type: application/json" \
  -d '{"settings":{"changelogFeed":{"limit":20,"categories":["added","fixed","security"]}}}'

# a site: which changelog feeds the root
curl -X PATCH https://forumulate.com/api/orgs/$ORG/sites/$SITE \
  -H "Authorization: Bearer $TOKEN" -H "Forumulate-Version: 2027-03-01" -H "content-type: application/json" \
  -d '{"config":{"changelogFeed":{"spaceId":"'$SPACE'","linkStyle":"changelog"}}}'

A space's key is checked strictly: a misspelled key is a 422 naming it. Add ?dryRun=true to see what a write would do without saving it. null removes the key.

In forumulate.yaml

The same keys, version 2: a space's under spaces[].settings.changelogFeed, a site's under sites[].config.changelogFeed.

version: 2
spaces:
  - slug: changelog
    type: CHANGELOG
    settings:
      changelogFeed:
        limit: 20
        breakingTags: [breaking, migration]
sites:
  - slug: main
    spaces: [changelog]
    config:
      changelogFeed:
        spaceId: <the changelog space's id>

Using it

# everything that broke, since January
curl 'https://acme.com/changelog.json?since=2026-01-01&type=security,fixed' \
  | jq '.items[] | select(.breaking) | {version, title, url}'

# a release-notes step in CI
curl -s 'https://acme.com/changelog.json?limit=1' | jq -r '.items[0].content_text'

A changelog's index page advertises the document with <link rel="alternate" type="application/json"> in its head, next to the RSS, Atom and JSON Feed links. A site's front page does the same once its changelogFeed names a space or switches the document on.

In the API

Every operation below is also reachable from the other surfaces without a dedicated command. forumulate api <Operation> calls it from the CLI, the JavaScript SDK has a typed method for each one (the last column names it; the Python and Go SDKs are generated from the same spec), and an agent reaches the organization-scoped ones through the MCP agent server — its api_* family tools, or call_api. Where a CLI command or a hand-made MCP tool exists too, the last column names it. Operation names link to the API reference.

Public Changelog Document

Operation

Request

What it does

Shortcuts

PublicChangelogDocument_spaceJson

GET /public/orgs/{orgId}/spaces/{spaceId}/changelog/changelog.json

Read the changelog as a JSON document

client.changelog.publicDocument.spaceJson()

PublicChangelogDocument_spaceXml

GET /public/orgs/{orgId}/spaces/{spaceId}/changelog/changelog.xml

Read the changelog as an Atom document

client.changelog.publicDocument.spaceXml()

PublicChangelogDocument_siteJson

GET /public/orgs/{orgId}/sites/{siteSlug}/changelog.json

Read the site's changelog as a JSON document

client.changelog.publicDocument.siteJson()

PublicChangelogDocument_siteXml

GET /public/orgs/{orgId}/sites/{siteSlug}/changelog.xml

Read the site's changelog as an Atom document

client.changelog.publicDocument.siteXml()

The Forumulate teamUpdated