# Backfill the last 90 days (https://docs.chargeflow.io/docs/merchants/automation/backfill-90-days)



When you first connect to Chargeflow, your webhook only fires for disputes that arrive after setup. To catch up on disputes that predate it, page through the Disputes list endpoint and keep the ones created in the last 90 days. This recipe is a full, copy-paste-runnable backfill.

## Before you start [#before-you-start]

* Get your `API Access Key`. Every request sends it in the `x-api-key` header. See [Authentication](https://docs.chargeflow.io/docs/reference/api-fundamentals/authentication).
* All requests go to `https://api.chargeflow.io` under the `/public/2025-04-01/` path.

## How pagination works [#how-pagination-works]

The Disputes list endpoint uses offset-based pagination. See [Pagination](https://docs.chargeflow.io/docs/reference/api-fundamentals/pagination) for the `offset` and `limit` parameters and the shared pattern.

For this recipe, the key detail is that `offset` is a page index, not a row count (covered in the note below).

The response wraps the results in `disputes` and a `pagination` object:

```json title="Response"
{
  "disputes": [
    {
      "id": "66e6ea9ecd94925a9f8060d9",
      "source": "stripe",
      "source_id": "du_123456789",
      "created_at": "2024-02-10T12:00:00Z",
      "reason": "fraud",
      "due_by": "2024-02-20T12:00:00Z",
      "amount": 100,
      "currency": "USD",
      "status": "needs_response",
      "stage": "Chargeback"
    }
  ],
  "pagination": {
    "totalCount": 5400,
    "offset": 0,
    "limit": 100,
    "totalPages": 54
  }
}
```

| Field                   | Description                               |
| ----------------------- | ----------------------------------------- |
| `pagination.totalCount` | Total number of disputes available.       |
| `pagination.offset`     | The current page offset.                  |
| `pagination.limit`      | Number of disputes returned in this page. |
| `pagination.totalPages` | Total number of pages available.          |

## Steps [#steps]

<Steps>
  <Step>
    ### Fetch a single page [#fetch-a-single-page]

    Set `limit=100` to minimize the number of requests. Start at `offset=0`.

    <Tabs items="['curl', 'Node.js', 'Python']">
      <Tab value="curl">
        ```bash title="Terminal"
        curl -X GET "https://api.chargeflow.io/public/2025-04-01/disputes?offset=0&limit=100" \
          -H "x-api-key: YOUR_API_KEY"
        ```
      </Tab>

      <Tab value="Node.js">
        ```javascript title="Node.js"
        async function fetchPage(offset, limit = 100) {
          const url = `https://api.chargeflow.io/public/2025-04-01/disputes?offset=${offset}&limit=${limit}`;
          const res = await fetch(url, {
            headers: { 'x-api-key': process.env.CHARGEFLOW_API_KEY },
          });
          return res.json();
        }
        ```
      </Tab>

      <Tab value="Python">
        ```python title="Python"
        import os, requests

        def fetch_page(offset, limit=100):
            res = requests.get(
                "https://api.chargeflow.io/public/2025-04-01/disputes",
                headers={"x-api-key": os.environ["CHARGEFLOW_API_KEY"]},
                params={"offset": offset, "limit": limit},
            )
            return res.json()
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step>
    ### Page until you reach 90 days [#page-until-you-reach-90-days]

    Disputes come back newest first, so walk pages in order and stop once you cross the 90-day cutoff. Filter each page by `created_at` and break out as soon as a dispute is older than the window. Use `pagination.totalPages` as a hard stop so you never loop past the end.

    ```javascript title="Node.js"
    async function backfillLast90Days() {
      const limit = 100;
      const cutoff = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000);
      const recent = [];

      let offset = 0;
      let totalPages = 1;

      while (offset < totalPages) {
        const page = await fetchPage(offset, limit);
        totalPages = page.pagination.totalPages;

        let crossedCutoff = false;
        for (const dispute of page.disputes) {
          if (new Date(dispute.created_at) >= cutoff) {
            recent.push(dispute);
          } else {
            // Older than 90 days. Since results are newest first, we can stop.
            crossedCutoff = true;
            break;
          }
        }

        if (crossedCutoff) break;
        offset += 1;
      }

      return recent;
    }
    ```

    <Callout title="Note">
      `offset` is a page number, not a row count. Increment it by `1` per page, not by `limit`.
    </Callout>
  </Step>

  <Step>
    ### Process each dispute [#process-each-dispute]

    With the 90-day list in hand, write each dispute to your own store and trigger whatever follow-up you need. For example, enrich any open dispute that is still in `needs_response`.

    ```javascript title="Node.js"
    const recent = await backfillLast90Days();
    console.log(`Backfilled ${recent.length} disputes from the last 90 days`);

    for (const dispute of recent) {
      // Persist the dispute in your system
      await upsertDispute(dispute);

      // Optionally act on disputes that still need a response
      if (dispute.status === 'needs_response') {
        // See the "Automate a chargeback dispute" recipe for the response flow
      }
    }
    ```
  </Step>
</Steps>

## Be a good API citizen [#be-a-good-api-citizen]

* Use `limit=100` so a 90-day backfill takes the fewest possible requests.
* Run the backfill once at setup. After that, rely on the `dispute.created` webhook for new disputes rather than polling.
* If you need to re-run the backfill, space out the requests instead of firing all pages at once.

## Next steps [#next-steps]

<Cards>
  <Card title="Pagination" href="/docs/reference/api-fundamentals/pagination">
    The shared offset and limit pattern.
  </Card>

  <Card title="Automate a chargeback dispute" href="/docs/merchants/automation/automate-a-chargeback-dispute">
    Respond to the disputes you just backfilled.
  </Card>

  <Card title="Subscribe to Webhook Events" href="/docs/merchants/automation/subscribe-to-events">
    Stop polling once the backfill is done.
  </Card>
</Cards>
