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

# Extracting a delta

> Fetch only the records that changed since your last extraction.

Most integrations do not need the whole data set on every run. They need the records that changed since the last run, to reconcile what their webhooks did not deliver.

The `sort` parameter gives you that.

## The `sort` parameter

`sort` selects one column, and that column does two jobs: `from` and `to` filter it, and the result comes back ordered by it.

| `sort`                 | `from` and `to` filter      | Order of the result    |
| ---------------------- | --------------------------- | ---------------------- |
| `created_at` (default) | the creation time           | `created_at` ascending |
| `updated_at`           | the time of the last change | `updated_at` ascending |

A request that does not send `sort` uses `created_at`.

<Info>
  Two endpoints accept `sort`: **Clients** and **Sales Associates**. Their records change after they are created.

  The other endpoints answer `422` when a request sends `sort`. Their records do not change after they are created, so their `updated_at` repeats their `created_at`.
</Info>

## The recipe

<Steps>
  <Step title="Keep a watermark">
    Store the highest `updated_at` that you processed. On your first run, use a time far enough in the past to cover the period that you need.
  </Step>

  <Step title="Ask for the changes">
    Send `sort=updated_at` and `from=<your watermark>`.

    ```console theme={null}
    $ curl -H "Authorization: Bearer $API_KEY" \
        "https://api.bspk.com/api/extraction/v1/clients?sort=updated_at&per_page=1000&from=2026-09-01T00:00:00Z"
    ```

    Subtract a few minutes from your watermark. A record is stamped when the database writes it, and becomes visible when the transaction commits. A slow transaction can therefore become visible after you read past its time, and the overlap collects it.
  </Step>

  <Step title="Read the next page with `from`, not with `page`">
    Take the `updated_at` of the last record that you received, send it as the `from` of your next call, and ask for `page=1` again.

    ```console theme={null}
    $ curl -H "Authorization: Bearer $API_KEY" \
        "https://api.bspk.com/api/extraction/v1/clients?sort=updated_at&per_page=1000&from=2026-09-17T14:39:26.000Z"
    ```

    Repeat until a call returns fewer records than your `per_page`.
  </Step>

  <Step title="Save each record with an upsert">
    Upsert on `bspk_id`. Never insert without a check.

    The same record can arrive more than once. The `from` bound includes the given time, so every call after the first returns the record that you used as the cursor, and a record that changes again during your pull is returned again.
  </Step>

  <Step title="Move the watermark last">
    Move your watermark to the highest `updated_at` that you received, and only after the whole pull succeeds. If a call fails, run the pull again from the old watermark.
  </Step>
</Steps>

## Paginating a delta safely

**Do not use `page=2`, `page=3` and so on with `sort=updated_at`. Ask for `page=1` every time, and move `from` forward instead.**

<CodeGroup>
  ```console Correct theme={null}
  # call 1
  $ curl ".../clients?sort=updated_at&per_page=1000&from=2026-09-01T00:00:00Z"
  #   -> the last record has updated_at = 2026-09-17T14:39:26.000Z

  # call 2: the last updated_at becomes the new `from`
  $ curl ".../clients?sort=updated_at&per_page=1000&from=2026-09-17T14:39:26.000Z"

  # repeat until a call returns fewer records than per_page
  ```

  ```console Loses records theme={null}
  # call 1
  $ curl ".../clients?sort=updated_at&per_page=1000&page=1"

  # call 2
  $ curl ".../clients?sort=updated_at&per_page=1000&page=2"
  ```
</CodeGroup>

<Warning>
  Adding a `to` bound does not make `page` safe for a delta.
</Warning>

<Info>
  The default `sort=created_at` is not affected. A creation time never changes, so `page=2`, `page=3` and so on are stable. Use `page` for a full export, and `from` for a delta.
</Info>

## Sizing the job

`x-total-count` tells you how many records match your filter. When the first call reports a count at or below your `per_page`, that one call is the whole delta.

`per_page` accepts up to `1000`.

## What moves `updated_at`

On a client, `updated_at` moves for more reasons than an edit to the profile. An appointment, an email, a call, a wishlist, a store visit, a special order and a rating all touch the record.

Your delta therefore includes clients whose own fields did not change.
