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

# Stay in sync with the change feed

> Poll the change feed to find out what changed, and recover after an outage.

You do not need to re-read every record to stay current. The [change feed](/change-feed) lists the changes PayPathIQ has committed for your Integration and Client, in order. This guide shows a polling loop. Finish the [quickstart](/quickstart) first so you have a key and a `clientCode`.

## What you get

Each event names the resource, the kind of change and the accepted revision. It carries no Customer or financial detail. To see the detail, read the resource with its own `GET`. The feed needs at least one of `customers:read`, `receivables:read`, `payment_requests:read` or `payment_observations:read`, and shows only the resource types those scopes cover.

## Step 1: Read a page

Request the feed without a cursor to start at the oldest retained change.

<CodeGroup>
  ```bash curl theme={null}
  curl --url "$PAYPATHIQ_BASE_URL/v1/clients/$PAYPATHIQ_CLIENT_CODE/changes?limit=100" \
    --header "Authorization: Bearer $PAYPATHIQ_SANDBOX_API_KEY"
  ```

  ```javascript JavaScript theme={null}
  const base = "https://api.sandbox.paypathiq.com";
  const clientCode = process.env.PAYPATHIQ_CLIENT_CODE;
  const headers = { Authorization: `Bearer ${process.env.PAYPATHIQ_SANDBOX_API_KEY}` };

  async function readPage(cursor) {
    const query = new URLSearchParams({ limit: "100" });
    if (cursor) query.set("cursor", cursor);
    const response = await fetch(`${base}/v1/clients/${clientCode}/changes?${query}`, { headers });
    return response.json();
  }
  ```

  ```python Python theme={null}
  import os
  import requests

  base = "https://api.sandbox.paypathiq.com"
  client_code = os.environ["PAYPATHIQ_CLIENT_CODE"]
  headers = {"Authorization": f"Bearer {os.environ['PAYPATHIQ_SANDBOX_API_KEY']}"}


  def read_page(cursor=None):
      params = {"limit": 100}
      if cursor:
          params["cursor"] = cursor
      response = requests.get(
          f"{base}/v1/clients/{client_code}/changes", headers=headers, params=params, timeout=30
      )
      return response.json()
  ```
</CodeGroup>

**Expect `200 OK`** with `data` (the events), `nextCursor` and `hasMore`. An event looks like this:

```json theme={null}
{
  "eventId": "6f1c2d4e-8a3b-4c5d-9e7f-0a1b2c3d4e5f",
  "type": "receivable.updated",
  "resource": { "type": "receivable", "id": "REC_B001" },
  "revision": 2,
  "occurredAt": "2026-10-15T09:30:00.000Z"
}
```

## Step 2: Process, store the cursor, repeat

1. Process every event in `data`. Delivery is at least once, so skip an `eventId` you have already handled.
2. Store `nextCursor` after you finish the page. Treat it as opaque.
3. Send it as `cursor` on the next request. Keep going while `hasMore` is `true`. When it is `false`, wait and poll again at your own pace.

`nextCursor` is present even on an empty page.

## Step 3: Read what changed

For each event, `GET` the resource named in `resource`. For example, read the Receivable by its ID. That read is authorized when you make it.

## Step 4: Recover from an expired cursor

Changes are kept for 30 days. A cursor older than that returns [`410 change_cursor_expired`](/problems/change_cursor_expired). To recover:

1. Request `GET /v1/clients/{clientCode}/changes?from=latest`. It returns no events and a `nextCursor` after every change committed so far.
2. Read the Customers and Receivables you need.
3. Resume from the cursor in step 1.

Taking the cursor first means a change that commits while you read is delivered by the feed instead of being missed.

## Add webhooks to poll sooner

[Webhooks](/webhooks) tell you promptly that something changed. They carry the same `eventId`. Keep the feed as the source of truth and use notifications only to poll sooner.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.