Goal

Read the managed changelog corpus (docs/releases/*.yaml) over HTTP to render your own release notes page, drive a CI bot, or sync release entries into a CRM. The endpoint serves the same data the docs-site widget consumes.

Prerequisites

None — the endpoint serves public-audience entries to anonymous callers. Authenticate with a cookie session + docs:read permission to also receive audience=admin entries.

Steps

1. List the latest entries

curl -X GET 'https://api.example.com/v1/public/changelog?lang=en&limit=25'

Response (200):

{
  "data": [
    {
      "id": "url-convention",
      "schema_version": 1,
      "release_version": "1.49.6",
      "audience": ["public"],
      "category": "breaking",
      "severity": "medium",
      "breaking": true,
      "title": {
        "en": "URL convention formalized + 13 path moves",
        "es": "Convención de URL formalizada + 13 paths movidos"
      },
      "summary": { "en": "...", "es": "..." },
      "permalink": "/changelog/v1.49.6/url-convention",
      "tags": ["api", "openapi", "routes"]
    }
  ],
  "meta": {
    "pagination": {
      "total": 1,
      "limit": 25,
      "has_more": false,
      "next_cursor": null
    },
    "audience": "public",
    "lang": "en"
  }
}

2. Filter by category, date range, or language

Query paramDescription
langen or es. Bilingual fields collapse to the requested locale.
audiencepublic (default for anon) or admin (requires cookie + docs:read).
categoryfeat, fix, breaking, deprecation, security, docs, infra, refactor.
since / untilISO 8601 date-time bounds (filter by release published_at).
limit1–100, default 25.
cursorOpaque base64 cursor from meta.pagination.next_cursor.
includesummary (default) or body (adds raw Markdown).
# Only breaking changes since the start of the year
curl 'https://api.example.com/v1/public/changelog?category=breaking&since=2026-01-01T00:00:00Z'

3. Paginate

The response includes meta.pagination.next_cursor. Pass it back as ?cursor=… on the next request to fetch the next page. The cursor is opaque (base64-encoded JSON internally) — do not parse it client-side.

curl 'https://api.example.com/v1/public/changelog?limit=10&cursor=eyJyZWxlYXNlX3ZlcnNpb24iOiIxLjQ3LjEi…'

4. ETag negotiation

The endpoint returns a weak ETag header. Send it back as If-None-Match on a subsequent request to receive 304 Not Modified without a body when the corpus has not changed:

curl -H 'If-None-Match: W/"a1b2c3d4"' \
     'https://api.example.com/v1/public/changelog'

Use this to drive lightweight polling without re-downloading the payload every cycle.

Errors

StatusCodeCause
400invalid_cursorCursor is malformed (truncated or tampered).
400invalid_since / invalid_untilDate param is not ISO 8601.
401unauthorizedRequested audience=admin without cookie auth.
403forbiddenCookie present but missing docs:read permission.
429rate_limitedPolling too aggressively.

Notes

  • Embargoed entries (embargo_until in the future) are excluded for anonymous and public-audience requests. Admins see them.
  • Unpublished entries (published_at: null) are excluded for public-audience requests. Admins see them.
  • See ADR-0090 (URL Convention) for the rationale behind the /v1/public/* namespace.

New in v1.49.6 — see ADR-0090.