How to fetch all contacts from HubSpot API when there are more than 100 records

I need to export all my contacts into a single JSON file. The problem is that HubSpot API only returns 100 contacts per request, so I have to handle pagination properly.

I looked at some solutions online but most use external libraries. I want to keep it simple with just basic PHP code since I only need this one feature.

Here’s what I tried but it gets stuck in an infinite loop. I think the issue is with how I’m handling the offset values:

<?php

function fetchContacts($startOffset = 0) {
    $fields = "&property=email&property=lastname&property=pipeline&property=contact_status&property=created_date";
    $key = "your-api-key-here";
    $endpoint = "https://api.hubapi.com/contacts/v1/lists/all/contacts/recent?hapikey=" . $key . $fields . '&vidOffset=' . $startOffset;
    
    $ch = curl_init();
    curl_setopt_array($ch, array(
        CURLOPT_URL => $endpoint,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_ENCODING => "",
        CURLOPT_MAXREDIRS => 10,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
        CURLOPT_CUSTOMREQUEST => "GET",
        CURLOPT_HTTPHEADER => array(
            "cache-control: no-cache"
        ),
    ));
    
    $result = curl_exec($ch);
    $contactData = json_decode($result);
    
    return $contactData;
}

$completeData = array();
$currentOffset = 0;
$continueLoop = true;

while ($continueLoop === true) {
    $apiResponse = fetchContacts($currentOffset);
    $completeData[] = $apiResponse;
    
    $currentOffset = $apiResponse->{'vid-offset'};
    $continueLoop = $apiResponse->{'has-more'};
    
    var_dump($continueLoop);
    $continueLoop = false; // temporary to prevent infinite loop
}

What am I doing wrong with the pagination logic?

Been there! Your API endpoint’s the problem - /recent is wonky with pagination. Switch to /all/contacts/all instead, it handles offsets much better. Also double-check that vid-offset is actually incrementing between calls. Sometimes it returns the same value and you get stuck in a loop.

Your main problem is you’re not combining the contact data right. You’re pushing the entire API response into your array instead of just grabbing the contacts array from each response. The vid-offset property is tricky too - sometimes the API returns it as null or zero even when there’s more data, which causes that infinite loop you mentioned. Here’s how to fix it:

$completeData = array();
$currentOffset = 0;
$hasMore = true;

while ($hasMore) {
    $apiResponse = fetchContacts($currentOffset);
    
    if (isset($apiResponse->contacts)) {
        $completeData = array_merge($completeData, $apiResponse->contacts);
    }
    
    $hasMore = $apiResponse->{'has-more'};
    $currentOffset = $apiResponse->{'vid-offset'};
    
    // Safety check to prevent infinite loops
    if (empty($apiResponse->contacts)) {
        $hasMore = false;
    }
}

Two key changes: use array_merge() to combine the actual contacts arrays, and add a safety check for empty responses. This gives you all contacts in one flat array instead of nested response objects.

This is a common HubSpot pagination issue. Your offset handling looks right, but there’s a sneaky problem with the has-more property check. Sometimes the API returns has-more as the string “false” instead of boolean false, so your loop keeps running forever. I’ve hit this exact bug before. Fix it by adding explicit type checking: php $continueLoop = ($apiResponse->{'has-more'} === true || $apiResponse->{'has-more'} === 'true'); Also, don’t store the entire API response - just grab the contacts array. Replace $completeData[] = $apiResponse; with $completeData = array_merge($completeData, $apiResponse->contacts); to get a clean contacts array. One more tip: add a counter as a failsafe during testing. Increment it each loop and break if it hits something reasonable to avoid runaway loops.