> ## Documentation Index
> Fetch the complete documentation index at: https://docs.financialdatapi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sync & changes

> Keep a downstream copy of Financial Data API fresh: check the manifest, pull a snapshot, monitor source health, and sync incrementally with the changes feed.

If you maintain a local copy of Financial Data API data, you do not need to refetch everything. Four endpoints let you detect change cheaply and pull only what moved:

<Columns cols={2}>
  <Card title="Manifest" icon="fingerprint" href="#1-check-the-manifest">
    `GET /manifest` is a cheap state fingerprint: a hash, the latest run, freshness markers, and source health.
  </Card>

  <Card title="Changes" icon="code-compare" href="#3-sync-incrementally">
    `GET /observations/changes` returns only observations that became known since a knowledge-time bound.
  </Card>

  <Card title="Snapshot" icon="camera" href="#full-snapshot">
    `GET /snapshot` returns the current public observation set plus the matching manifest hash.
  </Card>

  <Card title="Source health" icon="heart-pulse" href="#monitor-source-health">
    `GET /source-health` reports per-connector freshness so you can detect silently-frozen feeds.
  </Card>
</Columns>

## The sync loop

<Steps>
  <Step title="Seed once with a snapshot" icon="camera">
    Pull `GET /snapshot` to load the current public observation set, and store the `manifestHash` and `asOf` it returns.
  </Step>

  <Step title="Poll the manifest" icon="fingerprint">
    On a schedule, call `GET /manifest`. If `manifestHash` is unchanged from your stored value, nothing moved and you can stop early.
  </Step>

  <Step title="Pull only changes" icon="code-compare">
    When the hash changes, call `GET /observations/changes?start_date=<your last sync date>` to fetch only observations that became known on or after that date. Page through with the cursor.
  </Step>

  <Step title="Watch source health" icon="heart-pulse">
    Periodically read `GET /source-health` to catch stale or failing connectors before they show up as silently missing data.
  </Step>
</Steps>

## 1. Check the manifest

The manifest is the cheapest way to ask "did anything change?". Compare the returned `manifestHash` to the one you stored last; if it matches, you are up to date.

<ParamField query="source_id" type="string">Scope health and freshness to one source.</ParamField>
<ParamField query="provider_id" type="string">Scope health and freshness to one provider.</ParamField>

<CodeGroup>
  ```bash cURL theme={"theme":"css-variables"}
  curl "https://api.financialdatapi.com/manifest" \
    -H "x-api-key: $FINANCIALDATA_API_KEY"
  ```

  ```python Python theme={"theme":"css-variables"}
  import requests

  BASE = "https://api.financialdatapi.com"
  headers = {"x-api-key": "your_key_here"}

  res = requests.get(f"{BASE}/manifest", headers=headers)
  res.raise_for_status()
  manifest = res.json()["data"]
  print(manifest["manifestHash"], manifest["latestObservationAt"])
  ```

  ```typescript TypeScript theme={"theme":"css-variables"}
  import { FinancialDataApiClient } from "@financialdatapi/client";

  const client = new FinancialDataApiClient({ apiKey: process.env.FINANCIALDATA_API_KEY! });

  const { data: manifest } = await client.request("/manifest");
  console.log(manifest.manifestHash, manifest.latestObservationAt);
  ```
</CodeGroup>

```json Response theme={"theme":"css-variables"}
{
  "data": {
    "manifestHash": "9f1c...e7",
    "asOf": "2026-06-24T08:00:00Z",
    "latestRunId": "run_...",
    "observationCount": 4800000,
    "latestObservationAt": "2026-06-24T07:55:00Z",
    "latestIngestedAt": "2026-06-24T07:56:12Z",
    "sourceHealth": [
      {
        "connectorId": "bls-cpi",
        "sourceId": "bls",
        "providerId": "bls",
        "status": "healthy",
        "freshnessStatus": "fresh",
        "lastCompletedAt": "2026-06-24T07:50:00Z"
      }
    ]
  },
  "meta": { "api_version": "v1" },
  "requestId": "..."
}
```

<ResponseField name="manifestHash" type="string">Fingerprint of current state. Store it; an unchanged hash means nothing moved.</ResponseField>
<ResponseField name="asOf" type="string">Timestamp the manifest was generated (ISO). Record it so you know the date to pass as `start_date` on your next incremental pull.</ResponseField>
<ResponseField name="latestRunId" type="string">Id of the most recent ingestion run. May be null.</ResponseField>
<ResponseField name="observationCount" type="integer">Total observations in scope.</ResponseField>
<ResponseField name="latestObservationAt" type="string">Most recent `observedAt` (when a value became known) across the set.</ResponseField>
<ResponseField name="latestIngestedAt" type="string">Most recent ingestion timestamp across the set.</ResponseField>
<ResponseField name="sourceHealth" type="array">Per-connector health summaries (same shape as `/source-health`).</ResponseField>

<Tip>
  The manifest hash changes whenever the latest run, the latest observation/ingestion times, or source health change. Use it as a coarse change gate, then use the changes feed for the actual diff.
</Tip>

## Full snapshot

`GET /snapshot` returns the current public observation set together with the manifest hash that describes it. Use it to seed a fresh downstream copy, or to reconcile if you suspect drift.

<CodeGroup>
  ```bash cURL theme={"theme":"css-variables"}
  curl "https://api.financialdatapi.com/snapshot?country=USA" \
    -H "x-api-key: $FINANCIALDATA_API_KEY"
  ```

  ```python Python theme={"theme":"css-variables"}
  res = requests.get(
      f"{BASE}/snapshot",
      params={"country": "USA"},
      headers=headers,
  )
  res.raise_for_status()
  snapshot = res.json()["data"]
  print(snapshot["manifestHash"], len(snapshot["observations"]))
  ```

  ```typescript TypeScript theme={"theme":"css-variables"}
  const { data: snapshot } = await client.request("/snapshot", { country: "USA" });
  console.log(snapshot.manifestHash, snapshot.observations.length);
  ```
</CodeGroup>

```json Response theme={"theme":"css-variables"}
{
  "data": {
    "runId": null,
    "asOf": "2026-06-24T08:00:00Z",
    "manifestHash": "9f1c...e7",
    "observations": [
      {
        "observationId": "obs_us_cpi_2026_05",
        "indicatorId": "cpi_inflation_yoy",
        "country": "USA",
        "period": "2026-05",
        "actual": 3.1,
        "observedAt": "2026-06-11T12:30:00Z",
        "freshnessStatus": "fresh"
      }
    ],
    "releases": [],
    "sourceHealth": [ { "connectorId": "bls-cpi", "status": "healthy" } ]
  },
  "meta": { "api_version": "v1" },
  "requestId": "..."
}
```

<ResponseField name="asOf" type="string">When the snapshot was generated (ISO).</ResponseField>
<ResponseField name="manifestHash" type="string">Hash describing exactly this snapshot. Store it as your sync baseline.</ResponseField>
<ResponseField name="observations" type="array">The public observation set in scope. Each observation carries attribution, freshness, and rights metadata.</ResponseField>
<ResponseField name="sourceHealth" type="array">Per-connector health summaries.</ResponseField>

<Note>
  The snapshot returns only redistribution-safe public observations. Licensed vendor data is never included. Filter the scope with the standard observation filters (for example `country`, `indicator_id`).
</Note>

## 3. Sync incrementally

`GET /observations/changes` returns only observations whose knowledge time falls on or after a `start_date` bound, newest first. Pass the date of your last successful sync to fetch just the delta.

<ParamField query="start_date" type="string">Knowledge-time lower bound (date, `YYYY-MM-DD`). Returns observations whose `observedAt` is on or after this date. `as_of` is also accepted to retrieve the latest vintage known on or before a timestamp.</ParamField>
<ParamField query="country" type="string">Scope to one country (ISO 3).</ParamField>
<ParamField query="indicator_id" type="string">Scope to one or more canonical indicators (comma-separated).</ParamField>
<ParamField query="limit" type="integer" default="100">Page size, 1 to 500.</ParamField>
<ParamField query="cursor" type="string">Opaque pagination cursor from `meta.pagination.next_cursor`.</ParamField>

<CodeGroup>
  ```bash cURL theme={"theme":"css-variables"}
  curl "https://api.financialdatapi.com/observations/changes?start_date=2026-06-24&limit=200" \
    -H "x-api-key: $FINANCIALDATA_API_KEY"
  ```

  ```python Python theme={"theme":"css-variables"}
  def sync_changes(start_date: str):
      cursor = None
      while True:
          params = {"start_date": start_date, "limit": 500}
          if cursor:
              params["cursor"] = cursor
          res = requests.get(f"{BASE}/observations/changes", params=params, headers=headers)
          res.raise_for_status()
          body = res.json()
          for obs in body["data"]:
              upsert(obs)  # write into your store, keyed by observationId
          pagination = body["meta"]["pagination"]
          if not pagination["has_more"]:
              break
          cursor = pagination["next_cursor"]

  sync_changes("2026-06-24")
  ```

  ```typescript TypeScript theme={"theme":"css-variables"}
  // The SDK auto-paginates over cursor pages.
  for await (const obs of client.paginate("/observations/changes", {
    start_date: "2026-06-24",
  })) {
    upsert(obs); // write into your store, keyed by observationId
  }
  ```
</CodeGroup>

<Warning>
  Revisions appear in the changes feed as observations becoming known, not as in-place edits. Upsert by `observationId` and keep prior vintages if you need point-in-time history; Financial Data API retains revisions and never overwrites them.
</Warning>

<Info>
  Honest caveat on knowledge time: `as_of` currently approximates the ingestion timestamp rather than full provider-vintage reconstruction. The changes feed reflects when Financial Data API learned of a value, which is the right basis for incremental sync.
</Info>

## Monitor source health

`GET /source-health` reports per-connector status and freshness so you can detect a feed that has gone stale or started failing, before it shows up downstream as quietly missing data.

<ParamField query="source_id" type="string">Filter to one source.</ParamField>
<ParamField query="provider_id" type="string">Filter to one provider.</ParamField>
<ParamField query="limit" type="integer" default="100">Page size, 1 to 500.</ParamField>

<CodeGroup>
  ```bash cURL theme={"theme":"css-variables"}
  curl "https://api.financialdatapi.com/source-health" \
    -H "x-api-key: $FINANCIALDATA_API_KEY"
  ```

  ```python Python theme={"theme":"css-variables"}
  res = requests.get(f"{BASE}/source-health", headers=headers)
  res.raise_for_status()
  for src in res.json()["data"]:
      if src["status"] != "healthy" or src["freshnessStatus"] == "stale":
          print(src["connectorId"], src["status"], src["freshnessStatus"], src["issueCodes"])
  ```

  ```typescript TypeScript theme={"theme":"css-variables"}
  for await (const src of client.paginate("/source-health")) {
    if (src.status !== "healthy" || src.freshnessStatus === "stale") {
      console.warn(src.connectorId, src.status, src.issueCodes);
    }
  }
  ```
</CodeGroup>

```json Response theme={"theme":"css-variables"}
{
  "data": [
    {
      "connectorId": "bls-cpi",
      "sourceId": "bls",
      "providerId": "bls",
      "status": "healthy",
      "lastRunId": "run_...",
      "lastSuccessfulRunId": "run_...",
      "lastCompletedAt": "2026-06-24T07:50:00Z",
      "lastSuccessAt": "2026-06-24T07:50:00Z",
      "issueCodes": [],
      "lastError": null,
      "latestObservedAt": "2026-06-11T12:30:00Z",
      "latestPeriodEnd": "2026-05-31",
      "freshnessStatus": "fresh",
      "recordsInserted": 12,
      "recordsUpdated": 0,
      "warningCount": 0,
      "errorCount": 0,
      "nextRunAt": "2026-06-24T08:50:00Z"
    }
  ],
  "meta": {
    "api_version": "v1",
    "pagination": { "limit": 100, "cursor": null, "next_cursor": null, "has_more": false }
  },
  "requestId": "..."
}
```

<ResponseField name="connectorId" type="string">Connector identifier.</ResponseField>
<ResponseField name="status" type="string">Connector status, e.g. `healthy`, `degraded`, `stale`, `disabled`, `never_run`, `unknown`.</ResponseField>
<ResponseField name="freshnessStatus" type="string">Per-frequency freshness verdict for the feed: `fresh`, `stale`, or `unknown`.</ResponseField>
<ResponseField name="lastCompletedAt" type="string">When the last run finished (ISO). May be null.</ResponseField>
<ResponseField name="lastSuccessAt" type="string">When the connector last succeeded. Null if it has not succeeded recently.</ResponseField>
<ResponseField name="issueCodes" type="array">Machine-readable issue codes, e.g. `stale_source`, `connector_errors`, `connector_warnings`.</ResponseField>
<ResponseField name="lastError" type="string">Most recent error message, when present.</ResponseField>
<ResponseField name="latestObservedAt" type="string">Newest knowledge-time the connector has produced.</ResponseField>
<ResponseField name="latestPeriodEnd" type="string">Newest period the connector has covered.</ResponseField>
<ResponseField name="nextRunAt" type="string">When the connector is next scheduled to run. May be null.</ResponseField>

<Tip>
  For a per-series liveness verdict across every connector, also check `GET /ops/liveness`, which flags feeds that have silently stopped updating even when the connector itself reports healthy. It requires the `ops:read` scope.
</Tip>

## Putting it together

<AccordionGroup>
  <Accordion title="Recommended cadence" icon="clock" defaultOpen={true}>
    Seed with one snapshot. Poll the manifest on your refresh interval. When the hash changes, pull `observations/changes?start_date=<last sync date>` and upsert by `observationId`. Read source health on a slower cadence (for example hourly) to catch stalled feeds.
  </Accordion>

  <Accordion title="Idempotent upserts" icon="key">
    Always key your store by `observationId`. The changes feed can re-deliver an observation (for example after a revision), so upsert rather than insert, and never assume a value is final.
  </Accordion>

  <Accordion title="Rights stay enforced" icon="shield-halved">
    Snapshot and changes return only redistribution-safe public observations. Licensed vendor data never appears, so a synced copy inherits the same redistribution-safe boundary.
  </Accordion>

  <Accordion title="Pagination" icon="list-ol">
    Every list response carries `meta.pagination`. Follow `next_cursor` while `has_more` is true. The TypeScript SDK does this for you via async iteration.
  </Accordion>
</AccordionGroup>

## Related

<Columns cols={2}>
  <Card title="Derived analytics" icon="function" href="/apis/derived-analytics">
    Derived datasets sync alongside raw observations and carry their own provenance.
  </Card>

  <Card title="SEC fundamentals" icon="building-columns" href="/apis/companies">
    Keep a downstream copy of company fundamentals fresh the same way.
  </Card>
</Columns>
