{
  "schema_version": 1,
  "description": "Dated changes to the public API surface (datasets, series, research, MCP, keys). Reconstructed from the git history of api/ and tools/build_openapi.py; one entry per change a client could observe. Newest first. type: added, changed, fixed, deprecated or removed.",
  "entries": [
    {"date": "2026-11-02", "type": "added", "summary": "Self-service key management: POST /api/keys/replace and POST /api/keys/revoke with {\"email\"} always answer 202 status=check_email; an address holding an active self-serve key is emailed a one-time link (30 minutes, single use) to /api/keys/confirm, whose GET changes nothing and whose POST replaces (new key shown once, old revoked, tier kept) or revokes. Error bodies carry error and code. Tagged 'Manage access' in /openapi.json as replaceApiKey and revokeApiKey.", "surface": "keys, openapi"},
    {"date": "2026-11-02", "type": "changed", "summary": "/api/series/{series_id}: per-row source precedence. On daily and intraday series the publisher-dated backfill wins a disagreement with the archive; a monthly archive row first seen before its month ended is marked archive_rejected, loses to the backfill and has no release_date. Observations gain archive_value alongside backfill_value when the two disagree. /api/series now exposes every rate-limit header through CORS.", "surface": "series"},
    {"date": "2026-11-01", "type": "changed", "summary": "scheduled_releases.json item value strings name the release time as \"HH:MM Beijing time (UTC+8)\" instead of \"HH:MM CST\". The field and its type are unchanged, so schema_version stays 1."},
    {"date": "2026-11-01", "type": "added", "summary": "GET /api/datasets list responses carry links {share_url (an /explore URL that reopens the query), csv_url (the same request as CSV), api_url, curl}. CSV exports append a computed citation column (publisher, original title, publication date, source_url, record id and permalink). topic=<slug> is the documented name of the reviewed topic filter; watch=<slug> stays accepted as its alias."},
    {"date": "2026-11-01", "type": "changed", "summary": "topic=export-controls is narrower: every term now names export control or dual-use items itself (export control, dual-use, 出口管制, 两用物项). Titles that matched only rare earth with export, entity list or unreliable entity are no longer returned."},
    {"date": "2026-11-01", "type": "added", "summary": "GET /api/changes?dataset=documents&since=<ISO-8601>: documents added or revised since a time, oldest first. Each item carries change_type (new, newly_discovered, revised) and change_at (UTC); each document appears once per walk, at its latest settled change. Continue with cursor=<next_cursor> or since=<next_since> (same signed token). A bad token is invalid_cursor; before database migration 043 the route answers 503 unavailable ('not yet enabled'). Tagged 'Retrieve changes' in /openapi.json; MCP get_changes reads the same feed.", "surface": "changes, mcp, openapi"},
    {"date": "2026-11-01", "type": "added", "summary": "/api/datasets?dataset=documents&mode=changes&since=: the underlying event feed (new and revised events, oldest first, next_since, 10-minute settle). Its items now also carry change_type and change_at.", "surface": "datasets"},
    {"date": "2026-11-01", "type": "added", "summary": "watch=<slug> (alias topic=<slug>) on /api/datasets, /api/changes, find_records, export_records and get_changes: reviewed bilingual topic filters (export-controls, data-security, property-support, semiconductors), a literal title match on listed terms. list_watchlists returns the terms and review dates. dataset=social&mode=topics groups hot-list appearances per (platform, topic) within a window of at most 92 days.", "surface": "datasets, changes, mcp"},
    {"date": "2026-11-01", "type": "added", "summary": "Document records gain publisher, acquisition_route, translated_title, first_seen, last_seen, observation_count, change_type, version_count, last_revised_at and content_hash; change_type= filters them. Detail (id=documents:N) adds observations[], observations_total and revisions[]. Social and indicator rows gain their contract fields (platform, board, collected_at, original_topic, translated_topic; series_id, release_date, revision_status). Field meanings and what a null means: /data/data-contract.json. Before database migrations 043/045 the new fields read null and are listed in unavailable_fields.", "surface": "datasets"},
    {"date": "2026-11-01", "type": "changed", "summary": "The 'Retrieve changes' and 'Inspect observations' task tags now cover real operations: GET /api/changes, and document observations[]/revisions[] on /api/datasets?id=.", "surface": "openapi"},
    {"date": "2026-11-01", "type": "added", "summary": "Every JSON error from /api/datasets and /api/series, and the 429 and 503 answers from the shared limiter and key check, now carries a top-level `code` (invalid_parameter, invalid_cursor, unauthorized, not_found, method_not_allowed, rate_limited, server_error, unavailable) beside the existing `error` string. The /api/* 404 keeps its nested error object and gains the same flat `code`. Codes are listed in /openapi.json under x-error-codes.", "surface": "datasets, series, errors"},
    {"date": "2026-11-01", "type": "added", "summary": "MCP and research API gain find_records, get_record, get_observations, get_changes, export_records and list_watchlists over the same reads as /api/datasets, /api/changes and /api/series. One definition per tool: the earlier monitoring copies of find_records and get_changes are merged into these.", "surface": "mcp, research"},
    {"date": "2026-11-01", "type": "added", "summary": "/openapi.json operations are tagged by task (Find records, Retrieve a record, Inspect observations, Export, Manage access), carry runnable curl samples, and publish the deprecation policy and this changelog.", "surface": "openapi"},
    {"date": "2026-11-01", "type": "added", "summary": "POST /api/key_signup answers with status, tier, limits, docs, OpenAPI and upgrade URLs and next_steps; errors use one envelope with stable error.code values.", "surface": "keys"},
    {"date": "2026-11-01", "type": "added", "summary": "GET /api/series and /api/series/{series_id}: one clean time series per indicator, oldest first, each observation marked origin=archive or origin=backfill.", "surface": "series"},
    {"date": "2026-11-01", "type": "changed", "summary": "A Pro key now unlocks the research API and MCP when its email matches a verified account; an unlinked key gets a 401 that says how to link it.", "surface": "research, mcp"},
    {"date": "2026-11-01", "type": "added", "summary": "Admitted responses carry X-RateLimit-Remaining, X-RateLimit-Reset and RateLimit-Limit/-Remaining/-Reset, the same headers a 429 sends.", "surface": "rate limits"},
    {"date": "2026-10-31", "type": "added", "summary": "/api/datasets?dataset=indicators&mode=release returns one row per (series_id, period) with previous_value and a revised flag; the series filter selects one series_id.", "surface": "datasets"},
    {"date": "2026-10-31", "type": "changed", "summary": "The tier history window is enforced on every read, not only when `from` is passed. Keyed responses carry X-RateLimit-Monthly-Used and -Remaining. Dataset rows carry title_lang and is_translated; social titles lose the leading rank number and gain item_key. OpenAPI publishes x-api-tiers generated from api/tiers.py.", "surface": "datasets, openapi"},
    {"date": "2026-09-14", "type": "added", "summary": "GET /api/datasets: filtered, cursor-paginated reads of the retained documents, social hot-list and indicator archive, with record detail by id and one-page CSV export.", "surface": "datasets"},
    {"date": "2026-08-30", "type": "changed", "summary": "API surface version is 2 (X-Api-Version: 2). Version 1 was an internal preview with no public consumers and was superseded in place without a notice period.", "surface": "all"},
    {"date": "2026-08-29", "type": "added", "summary": "JSON 404 for undocumented /api/* routes, Markdown content negotiation, RateLimit-* headers on 429, the X-Api-Version header and /.well-known/mcp.json.", "surface": "errors, docs, mcp"},
    {"date": "2026-08-29", "type": "added", "summary": "Developer portal, OpenAPI document and the structured {error: {code, message}} envelope on the research API and MCP.", "surface": "research, mcp, openapi"}
  ]
}
