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 param | Description |
|---|---|
lang | en or es. Bilingual fields collapse to the requested locale. |
audience | public (default for anon) or admin (requires cookie + docs:read). |
category | feat, fix, breaking, deprecation, security, docs, infra, refactor. |
since / until | ISO 8601 date-time bounds (filter by release published_at). |
limit | 1–100, default 25. |
cursor | Opaque base64 cursor from meta.pagination.next_cursor. |
include | summary (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
| Status | Code | Cause |
|---|---|---|
| 400 | invalid_cursor | Cursor is malformed (truncated or tampered). |
| 400 | invalid_since / invalid_until | Date param is not ISO 8601. |
| 401 | unauthorized | Requested audience=admin without cookie auth. |
| 403 | forbidden | Cookie present but missing docs:read permission. |
| 429 | rate_limited | Polling too aggressively. |
Notes
- Embargoed entries (
embargo_untilin 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.