See Stacksync in action with a solution architect. Book a demo ↗

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.

Create Resource dropdown listing Folder, Sync, Workflow, Queue, Variable, Connection and API Proxy, with "Variable" highlighted.
"Create a new Variable" dialog with Variable Name "cursor", region US East (N. Virginia) and value type "number".

The workflow

Pagination workflow: POST Trigger, Read Cursor, a loop with Return Iteration, Update Cursor, an "If has more" branch to Re Trigger or Update Cursor to 0.
  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.
"Trigger your workflow" Run tab showing the Example cURL POST request to the workflow's post_trigger run URL, highlighted for copying.
  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.    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),
           }
    "Read cursor" Python Code module with context body_cursor and var_cursor set to {{ WORKSPACE_VARIABLES.cursor }}, picked from Workspace Variables.
  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

    For Loop module configuration with "Break On Error" checked and the List field set to {{ read_cursor.page }}, marked by an arrow.
    Workflow Execution Logs with the For Loop's Child Runs iteration dropdown open, showing "Iteration 1" and "Iteration 2".
  5. Update Workspace Variable - set cursor to {{ read_cursor.next_cursor }}. This advances the saved cursor so the next run reads the new value.

    Action modules panel searched for "variable", listing workflow and workspace variable actions with "Workspace Variable - Update" highlighted.
    "Update Cursor" Workspace Variable - Update module setting variable cursor (number) to {{ read_cursor.next_cursor }} from the picker.
  6. If/Else on {{ read_cursor.has_more }} == true:

    • true → HTTP Request, POST to the trigger's webhook CURL and parse it
    "Re Trigger" HTTP Request module with the workflow's POST trigger cURL pasted into "Paste as cURL" and an arrow pointing to Parse.
    • 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:

Sourcenext cursormore pages?
GraphQL (Shopify)endCursorhasNextPage
Cursor REST (HubSpot)paging.next.afterpaging.next exists
Databaselast row's idfull 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.

On this page