> For the complete documentation index, see [llms.txt](https://docs.stacksync.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.stacksync.com/agentic-workflows/guides/paginating-with-a-workspace-variable.md).

# Paginating with a workspace variable

APIs return one page at a time. To get the next page you need a **cursor** that says where the last page ended. A `for` loop can't carry that cursor between runs, and looping through everything in one go hits the 300-second timeout.

The fix: do **one page per run**, save the cursor in a workspace variable, and have the workflow re-trigger itself until there are no pages left.

### Setup

Create a workspace variable called `cursor` (Variables & secrets), type `number`, value `0`. Reference it as `{{ WORKSPACE_VARIABLES.cursor }}` in the workflows.

<figure><img src="https://3389950191-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FO4RFXIz4V8iqghJ3JVSs%2Fuploads%2FlnGHpnlneAgULWkQJFlB%2Fimage.png?alt=media&amp;token=a6631d11-1c7e-4c0a-a2ae-5d8cccc7ace7" alt="" width="375"><figcaption></figcaption></figure>

<figure><img src="https://3389950191-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FO4RFXIz4V8iqghJ3JVSs%2Fuploads%2FInfP3hAiSv15quTZTNn1%2FScreenshot%202026-06-11%20at%202.04.32%E2%80%AFPM.png?alt=media&amp;token=e806eb1e-6127-44e3-9d42-5f9f09cbbb24" alt=""><figcaption></figcaption></figure>

### The workflow

<figure><img src="https://3389950191-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FO4RFXIz4V8iqghJ3JVSs%2Fuploads%2FTujyqh9EDkyBGkj7zYNR%2Fimage.png?alt=media&amp;token=5b90820a-f42b-4e26-9fab-1fadd363efe5" alt=""><figcaption></figcaption></figure>

1. **Trigger** - Choose the default trigger available when you open a new workflow. Copy the curl command as we will be using it to re-fire itself for each page.

<figure><img src="https://3389950191-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FO4RFXIz4V8iqghJ3JVSs%2Fuploads%2FBsSSjTBVTxo9R3V9zXaw%2Fimage.png?alt=media&amp;token=cb3d2dd0-122e-460d-a841-1e0fd5232b27" alt=""><figcaption></figcaption></figure>

1. **Input** - wire the trigger in.
2. **Read cursor** (Code) - get the cursor, fetch one page, output the next cursor and whether more pages exist. Bind `body_cursor` → `{{ input.body.cursor }}` and `var_cursor` → `{{ WORKSPACE_VARIABLES.cursor }}.`
3. &#x20;

   ```python
      def main(WORKFLOW_CONTEXT):
          raw = WORKFLOW_CONTEXT.get("cursor", "")
          cursor = int(raw) if str(raw).strip() not in ("", "None") else 0

          items = list(range(1, 13))   # replace with your real source
          page_size = 5
          page = items[cursor:cursor + page_size]

          return {
              "page": page,
              "next_cursor": cursor + page_size,
              "has_more": (cursor + page_size) < len(items),
          }
   ```

   <figure><img src="https://3389950191-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FO4RFXIz4V8iqghJ3JVSs%2Fuploads%2FYkcOTrxZ2JP88fF6Tszx%2Fimage.png?alt=media&amp;token=1d4ff66d-8f41-417a-a6f7-bdfd1af00b04" alt=""><figcaption></figcaption></figure>
4. **For loop** over `{{ read_cursor.page }}` — your per-record work. This loops through each index for the page and you can check each run in the child iterations of the execution logs

   <figure><img src="https://3389950191-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FO4RFXIz4V8iqghJ3JVSs%2Fuploads%2FThXj7x2ONRBYTrJ86Q56%2Fimage.png?alt=media&amp;token=dbf762d3-2db1-44b9-85c4-dfdb9619a73c" alt="" width="375"><figcaption></figcaption></figure>

   <figure><img src="https://3389950191-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FO4RFXIz4V8iqghJ3JVSs%2Fuploads%2FTsRb5IsU5yuHzA7htd09%2Fimage.png?alt=media&amp;token=334af0d7-e1d8-4d04-94f5-d23f8f7b4701" alt=""><figcaption></figcaption></figure>
5. **Update Workspace Variable** - set `cursor` to `{{ read_cursor.next_cursor }}`. This advances the saved cursor so the next run reads the new value.

   <figure><img src="https://3389950191-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FO4RFXIz4V8iqghJ3JVSs%2Fuploads%2FCoq62oy9d8i5WuldV49w%2Fimage.png?alt=media&amp;token=71e8f5e6-b2c2-4990-af59-0e51ce7a2291" alt=""><figcaption></figcaption></figure>

   <figure><img src="https://3389950191-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FO4RFXIz4V8iqghJ3JVSs%2Fuploads%2FevMgsJhXkuLtszrYO8P9%2Fimage.png?alt=media&amp;token=66ed6dc5-8214-439c-90bd-5c88b62eedba" alt=""><figcaption></figcaption></figure>
6. **If/Else** on `{{ read_cursor.has_more }} == true`:

   * **true** → **HTTP Request**, POST to the trigger's webhook CURL and parse it

   <figure><img src="https://3389950191-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FO4RFXIz4V8iqghJ3JVSs%2Fuploads%2F7UdkR4ff6yEvLexzAfmS%2Fimage.png?alt=media&amp;token=e6ff5c7d-ec2f-4151-a905-da503cd0c545" alt=""><figcaption></figcaption></figure>

   * **false** → **Update Workspace Variable** set `cursor` back to 0, then stop.

#### Swap in a real source

Replace the stand-in list in step 3. Only the cursor and the "more pages" check change:

| Source                | next cursor         | more pages?          |
| --------------------- | ------------------- | -------------------- |
| GraphQL (Shopify)     | `endCursor`         | `hasNextPage`        |
| Cursor REST (HubSpot) | `paging.next.after` | `paging.next` exists |
| Database              | last row's `id`     | full page returned   |

### Watch out for

* **Use the Update Workspace Variable node,** not "Update Workflow Variable." They look identical but write to different stores; mixing them up means your cursor never actually moves and the workflow loops forever on the same page.
* **Timeout:** keep the page size small so each run finishes well under 300 seconds.
