China SnapshotDocs
Get a key ↗
Open in AI ↗
Open in ChatGPTOpen in ClaudeView Markdown
API reference

Documents added or revised since a time

getChanges · GET /api/changes

Endpoint#

GET /api/changes

Polling feed over the retained documents: what we newly collected (new, newly_discovered) or saw change (revised) after since, oldest first, with change_at in UTC. Changes from the last 10 minutes are held back so a slow collector commit is never skipped. Same rows as /api/datasets?dataset=documents&mode=changes, collapsed to one item per document. Anonymous, no key; tiers bound the page size and history window.

Authentication#

json
[]

An empty security requirement means this operation does not declare authentication. Read operation notes for entitlement and protocol-method requirements.

Parameters#

NameInRequiredDescription
datasetqueryNoOnly documents has a change feed.
sincequeryNoRequired on the first call (or pass cursor instead): an ISO-8601 instant with a timezone, UTC recommended (2026-10-29T00:00:00Z). Inclusive. Compared with change_at, the time of the change on our collection clock (first collection for new and newly_discovered, the observation of the changed body for revised), not publication time. Must lie inside the tier history window (x-api-tiers history_days). A next_since token is also accepted here.
cursorqueryNonext_cursor (== next_since) from the previous page; do not combine with since. The walk resumes strictly after the last item returned, so no document repeats and none is skipped. A document that changes again later reappears once, at its new change. The token is signed; an altered or malformed one returns invalid_cursor.
limitqueryNoMaximum rows in this page. Anonymous callers may ask for up to 100; keyed tiers up to their rows_per_page (x-api-tiers).
sourcequeryNoCase-insensitive exact publisher label; use the response sources list.
qqueryNoLiteral case-insensitive substring of displayed/original title or publisher. No semantic search, query translation, or full-body search.
watchqueryNoAlias of topic (the older name). Passing both with different values is invalid_parameter.
topicqueryNoReviewed bilingual topic filter (documents, social): a case-insensitive title match on one of the listed terms, nothing inferred. export-controls is conservative: every term names export control or dual-use items itself. Terms and review dates: list_watchlists (MCP) or api/_watchlists.py.
fieldsqueryNoComma-separated unique field keys of the change item; defaults to all.
formatqueryNoCSV downloads one bounded page as UTF-8 with BOM and formula-safe string cells, never the entire matching archive. The selected fields are followed by a computed citation column.

Responses and errors#

HTTP statusMeaning
200One page of changes, oldest first. Each document appears once, at its latest settled change; change_type is new, newly_discovered (published more than 2 days, Beijing dates, before we first collected it) or revised.
400Invalid query, field, date or cursor. Body carries code: see x-error-codes.
405Use GET. Body carries code: see x-error-codes.
429Rate limit exceeded (60 requests per minute). Body carries code: see x-error-codes.
503The change feed is not yet enabled on this deployment (database migration 043 pending: code unavailable, error "not yet enabled"), or the archive is unavailable; retry later. Body carries code: see x-error-codes.

Response examples#

No response example is supplied by OpenAPI for this operation. The complete response schemas are preserved below; use an actual response for observed values.

Retry guidance#

See errors and retries. Honor operation-specific response headers. A POST may create state; do not replay it automatically.

Complete operation contract#

The expanded contract below includes all parameter constraints, response headers, schemas and examples from OpenAPI.

json
{
  "path": "/api/changes",
  "method": "GET",
  "parameters": [
    {
      "name": "dataset",
      "in": "query",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "documents"
        ],
        "default": "documents"
      },
      "description": "Only documents has a change feed."
    },
    {
      "name": "since",
      "in": "query",
      "required": false,
      "schema": {
        "type": "string",
        "format": "date-time"
      },
      "description": "Required on the first call (or pass cursor instead): an ISO-8601 instant with a timezone, UTC recommended (2026-10-29T00:00:00Z). Inclusive. Compared with change_at, the time of the change on our collection clock (first collection for new and newly_discovered, the observation of the changed body for revised), not publication time. Must lie inside the tier history window (x-api-tiers history_days). A next_since token is also accepted here."
    },
    {
      "name": "cursor",
      "in": "query",
      "required": false,
      "schema": {
        "type": "string"
      },
      "description": "next_cursor (== next_since) from the previous page; do not combine with since. The walk resumes strictly after the last item returned, so no document repeats and none is skipped. A document that changes again later reappears once, at its new change. The token is signed; an altered or malformed one returns invalid_cursor."
    },
    {
      "name": "limit",
      "in": "query",
      "required": false,
      "schema": {
        "type": "integer",
        "minimum": 1,
        "maximum": 5000,
        "default": 50
      },
      "description": "Maximum rows in this page. Anonymous callers may ask for up to 100; keyed tiers up to their rows_per_page (x-api-tiers)."
    },
    {
      "name": "source",
      "in": "query",
      "required": false,
      "schema": {
        "type": "string",
        "maxLength": 180
      },
      "description": "Case-insensitive exact publisher label; use the response sources list."
    },
    {
      "name": "q",
      "in": "query",
      "required": false,
      "schema": {
        "type": "string",
        "maxLength": 180
      },
      "description": "Literal case-insensitive substring of displayed/original title or publisher. No semantic search, query translation, or full-body search."
    },
    {
      "name": "watch",
      "in": "query",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "data-security",
          "export-controls",
          "property-support",
          "semiconductors"
        ]
      },
      "description": "Alias of topic (the older name). Passing both with different values is invalid_parameter."
    },
    {
      "name": "topic",
      "in": "query",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "data-security",
          "export-controls",
          "property-support",
          "semiconductors"
        ]
      },
      "description": "Reviewed bilingual topic filter (documents, social): a case-insensitive title match on one of the listed terms, nothing inferred. export-controls is conservative: every term names export control or dual-use items itself. Terms and review dates: list_watchlists (MCP) or api/_watchlists.py."
    },
    {
      "name": "fields",
      "in": "query",
      "required": false,
      "schema": {
        "type": "string"
      },
      "description": "Comma-separated unique field keys of the change item; defaults to all."
    },
    {
      "name": "format",
      "in": "query",
      "required": false,
      "schema": {
        "type": "string",
        "enum": [
          "json",
          "csv"
        ],
        "default": "json"
      },
      "description": "CSV downloads one bounded page as UTF-8 with BOM and formula-safe string cells, never the entire matching archive. The selected fields are followed by a computed citation column."
    }
  ],
  "operationId": "getChanges",
  "summary": "Documents added or revised since a time",
  "description": "Polling feed over the retained documents: what we newly collected (new, newly_discovered) or saw change (revised) after since, oldest first, with change_at in UTC. Changes from the last 10 minutes are held back so a slow collector commit is never skipped. Same rows as /api/datasets?dataset=documents&mode=changes, collapsed to one item per document. Anonymous, no key; tiers bound the page size and history window.",
  "tags": [
    "data",
    "Retrieve changes",
    "Export"
  ],
  "security": [],
  "responses": {
    "200": {
      "description": "One page of changes, oldest first. Each document appears once, at its latest settled change; change_type is new, newly_discovered (published more than 2 days, Beijing dates, before we first collected it) or revised.",
      "headers": {
        "X-Dataset-Row-Count": {
          "schema": {
            "type": "integer"
          },
          "description": "Rows in this response."
        },
        "X-Dataset-Next-Cursor": {
          "schema": {
            "type": "string"
          },
          "description": "Continuation cursor; empty at the end. Also exposed to cross-origin browsers."
        },
        "X-Dataset-Next-Since": {
          "schema": {
            "type": "string"
          },
          "description": "mode=changes and /api/changes: the next_since token; empty otherwise."
        },
        "X-Dataset-Export-Scope": {
          "schema": {
            "enum": [
              "page",
              "record"
            ]
          },
          "description": "Explicit export scope."
        }
      },
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "required": [
              "items",
              "row_count",
              "next_since",
              "next_cursor"
            ],
            "properties": {
              "schema_version": {
                "const": 1
              },
              "dataset": {
                "const": "documents"
              },
              "mode": {
                "const": "changes"
              },
              "endpoint": {
                "const": "/api/changes"
              },
              "as_of": {
                "type": "string",
                "format": "date-time"
              },
              "since": {
                "type": "string",
                "format": "date-time",
                "description": "The resolved start of this page (UTC)."
              },
              "items": {
                "type": "array",
                "maxItems": 5000,
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "properties": {
                    "id": {
                      "type": "string",
                      "pattern": "^documents:[1-9][0-9]*$",
                      "description": "The document the change is about. Appears at most once per walk."
                    },
                    "title": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Title"
                    },
                    "title_lang": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Title language"
                    },
                    "is_translated": {
                      "type": [
                        "boolean",
                        "null"
                      ],
                      "description": "Title is a machine translation"
                    },
                    "source": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Publisher"
                    },
                    "source_url": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Original source"
                    },
                    "change_type": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Change type",
                      "enum": [
                        "new",
                        "newly_discovered",
                        "revised",
                        null
                      ]
                    },
                    "change_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Change time (UTC)",
                      "format": "date-time"
                    },
                    "event": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Event"
                    },
                    "event_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Event time",
                      "format": "date-time"
                    },
                    "original_title": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Original title"
                    },
                    "published_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Publication date",
                      "format": "date"
                    },
                    "first_seen_at": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "First collected",
                      "format": "date-time"
                    },
                    "content_hash": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Hash of the extracted text at this event"
                    },
                    "language": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Original language"
                    }
                  },
                  "description": "One document at its latest settled event in the walk."
                }
              },
              "row_count": {
                "type": "integer",
                "minimum": 0,
                "maximum": 5000
              },
              "next_since": {
                "type": "string",
                "description": "Signed resume position after the last item; never null. Pass back as since (or as cursor)."
              },
              "next_cursor": {
                "type": "string",
                "description": "Same token as next_since, for clients that page with cursor=."
              },
              "has_more": {
                "type": "boolean",
                "description": "More settled changes exist after this page."
              },
              "change_types": {
                "type": "object",
                "description": "Counts of change_type in this page only.",
                "properties": {
                  "new": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "newly_discovered": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "revised": {
                    "type": "integer",
                    "minimum": 0
                  }
                }
              },
              "settle_minutes": {
                "type": "integer",
                "description": "Events newer than this are held back until settled."
              },
              "coverage": {
                "type": "object",
                "required": [
                  "kind",
                  "unit",
                  "date_field",
                  "date_timezone",
                  "order",
                  "notes"
                ],
                "properties": {
                  "kind": {
                    "const": "retained_archive"
                  },
                  "unit": {
                    "enum": [
                      "documents",
                      "document_events",
                      "hot_list_observations",
                      "platform_topics",
                      "indicator_observations",
                      "indicator_releases"
                    ]
                  },
                  "date_field": {
                    "enum": [
                      "collected_at",
                      "observed_at",
                      "first_seen_at",
                      "last_seen_at",
                      "event_at"
                    ]
                  },
                  "date_timezone": {
                    "const": "UTC"
                  },
                  "order": {
                    "enum": [
                      "date_desc_id_desc",
                      "event_at_asc"
                    ]
                  },
                  "notes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "text/csv": {
          "schema": {
            "type": "string"
          }
        }
      }
    },
    "400": {
      "description": "Invalid query, field, date or cursor. Body carries `code`: see x-error-codes.",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "required": [
              "error",
              "code"
            ],
            "properties": {
              "error": {
                "type": "string"
              },
              "retry_after": {
                "type": "integer"
              },
              "code": {
                "type": "string",
                "enum": [
                  "invalid_parameter",
                  "invalid_cursor",
                  "unauthorized",
                  "forbidden",
                  "not_found",
                  "method_not_allowed",
                  "rate_limited",
                  "server_error",
                  "unavailable"
                ],
                "description": "Stable machine code; see x-error-codes."
              }
            }
          }
        }
      }
    },
    "405": {
      "description": "Use GET. Body carries `code`: see x-error-codes.",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "required": [
              "error"
            ],
            "properties": {
              "error": {
                "type": "string"
              },
              "retry_after": {
                "type": "integer"
              }
            }
          }
        }
      }
    },
    "429": {
      "description": "Rate limit exceeded (60 requests per minute). Body carries `code`: see x-error-codes.",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "required": [
              "error"
            ],
            "properties": {
              "error": {
                "type": "string"
              },
              "retry_after": {
                "type": "integer"
              }
            }
          }
        }
      },
      "headers": {
        "Retry-After": {
          "schema": {
            "type": "integer"
          },
          "description": "Seconds until retry; also sends RateLimit-* and X-RateLimit-* headers."
        }
      }
    },
    "503": {
      "description": "The change feed is not yet enabled on this deployment (database migration 043 pending: code unavailable, error \"not yet enabled\"), or the archive is unavailable; retry later. Body carries `code`: see x-error-codes.",
      "content": {
        "application/json": {
          "schema": {
            "type": "object",
            "required": [
              "error"
            ],
            "properties": {
              "error": {
                "type": "string"
              },
              "retry_after": {
                "type": "integer"
              }
            }
          }
        }
      }
    }
  },
  "x-codeSamples": [
    {
      "lang": "curl",
      "label": "curl",
      "source": "curl -s 'https://china-snapshot.com/api/changes?dataset=documents&since=2026-10-29T00:00:00Z&limit=3'"
    },
    {
      "lang": "curl",
      "label": "curl",
      "source": "curl -s -o changes.csv 'https://china-snapshot.com/api/changes?dataset=documents&since=2026-10-29T00:00:00Z&watch=export-controls&format=csv&limit=20'"
    }
  ],
  "x-pagination": {
    "first_call": "since=<ISO-8601 instant with timezone>, inclusive",
    "continue": "cursor=<next_cursor> or since=<next_since> (the same signed token)",
    "repeats": "none within a walk: each document appears once, at its latest settled change",
    "end_of_results": "has_more is false; next_since stays valid for polling later",
    "settle_minutes": 10
  }
}

Search documentation