GuidesAPI Reference
Log In

How to retrieve all results from a paginated API

The code below provides an example in Python of how to paginate through all the results of a paginated API. It includes logic to back off if rate limiting becomes necessary

import aiohttp
import asyncio
from typing import Optional, List, Dict, Any


async def authenticate(url: str, headers: Dict[str, str]) -> Optional[str]:
    """Authenticate with the API and return the JWT token."""
    async with aiohttp.ClientSession() as session:
        async with session.post(url, headers=headers) as response:
            if response.status == 200:
                data = await response.json()
                api_jwt = data.get("access_token")
                print("Authentication successful.")
                return api_jwt
            else:
                print(f"Failed to authenticate: HTTP {response.status}")
                return None


async def fetch_all_pages(url: str, headers: Dict[str, str]) -> List[Any]:
    """Fetch all pages of data from the API, handling pagination and rate limiting."""
    results: List[Any] = []
    params = {"limit": 100}
    backoff_time = 1

    async with aiohttp.ClientSession() as session:
        while url:
            async with session.get(url, headers=headers, params=params) as response:
                if response.status == 429:
                    content = await response.text()
                    if "limit exceeded" in content.lower():
                        print("Usage quota exceeded. Aborting.")
                        break
                    elif "too many requests" in content.lower():
                        print(f"Rate limit exceeded. Retrying in {backoff_time} seconds.")
                        await asyncio.sleep(backoff_time)
                        backoff_time *= 2
                    else:
                        print(f"Unhandled 429 error: {content}")
                        break
                elif response.status != 200:
                    print(f"Failed to fetch data: HTTP {response.status}")
                    break
                else:
                    data = await response.json()
                    results.extend(data["results"])
                    print(f"Fetched {len(data['results'])} results from {url}")
                    url = data.get("next")
                    params = {}
                    backoff_time = 1

    return results


async def main():
    auth_url = "https://<API Server FQDN>/authenticate"
    auth_headers = {
        "Accept": "application/json",
        "User-Agent": "YourUserAgent/1.0",
        "Authorization": "Bearer <Your Public API Key>",
    }

    jwt = await authenticate(auth_url, auth_headers)
    if jwt:
        # Modify api_url to target the intended API
        api_url = "https://<API Server FQDN>/api/sales/v1/customer/"
        api_headers = {
            "Accept": "application/json",
            "X-Active-Store": "1",
            "User-Agent": "YourUserAgent/1.0",
            "Authorization": f"Bearer {jwt}",
        }

        all_results = await fetch_all_pages(api_url, api_headers)
        print(f"Total results fetched: {len(all_results)}")
    else:
        print("Failed to authenticate. No data fetched.")

# Run the main function
asyncio.run(main())
import axios, {AxiosError, AxiosResponse} from 'axios';

interface ApiResponse {
    results: any[];
    next?: string;
}
interface AuthHeaders {
  [key: string]: string; // This index signature allows any string key with a string value.
  Accept: string;
  'User-Agent': string;
  Authorization: string;
}

// Define an interface for the expected structure of the authentication response data
interface AuthResponseData {
  access_token: string; // Add other properties if the response contains more
}

// Extend the AxiosResponse interface with the specific data type for authentication
interface AuthResponse extends AxiosResponse {
  data: AuthResponseData;
}

// Define the function with the correct expected response type
async function authenticate(url: string, headers: AuthHeaders): Promise<string | null> {
  try {
    // Cast the response to AuthResponse for proper type checking
    const response = await axios.post<AuthResponseData>(url, {}, { headers }) as AuthResponse;
    if (response.status === 200) {
      console.log("Authentication successful.");
      return response.data.access_token; // TypeScript now knows that access_token exists on response.data
    } else {
      console.log(`Failed to authenticate: HTTP ${response.status}`, response.data);
      return null;
    }
  } catch (error) {
    handleError(error as AxiosError, "Authentication error");
    return null;
  }
}

async function fetchAllPages(url: string, headers: AuthHeaders): Promise<any[]> {
    let results: any[] = [];
    let params = { limit: 100 };
    let backoffTime = 1;

    while (url) {
        try {
            const response = await axios.get<ApiResponse>(url, { headers, params });
            results = [...results, ...response.data.results];
            console.log(`Fetched ${response.data.results.length} results from ${url}`);
            url = response.data.next || '';
            params = { limit: 100 }; // Reset to initial structure with 'limit'
            backoffTime = 1; // Reset backoff time after a successful request
        } catch (error: any) {
            // Correctly await the async function here
            const shouldContinue = await handleRateLimit(error, backoffTime);
            if (!shouldContinue) break;

            // If rate limit was hit, increment the backoff time
            if (axios.isAxiosError(error) && error.response?.status === 429) {
                backoffTime *= 2;
            }
        }
    }
    return results;
}


async function handleRateLimit(error: any, backoffTime: number): Promise<boolean> {
  if (axios.isAxiosError(error) && error.response?.status === 429) {
    // Check the message within the response data to determine the type of rate limit error
    const errorMessage: string = error.response.data.message;

    if (errorMessage.toLowerCase().includes("too many requests")) {
      console.log(`Rate limit exceeded: too many requests. Retrying in ${backoffTime} seconds.`);
      await new Promise(resolve => setTimeout(resolve, backoffTime * 1000));
      return true; // Return true to retry
    } else if (errorMessage.toLowerCase().includes("limit exceeded")) {
      console.log(`Quota exceeded: Daily limit reached. Aborting.`);
      return false; // Return false to abort
    }
  } else if (axios.isAxiosError(error)) {
    console.error(`Failed to fetch data: HTTP ${error.response?.status}`, error.response.data);
  } else {
    console.error(`Unexpected error: ${error}`);
  }
  return false; // Default return value for other errors or if no response status is 429
}


function handleError(error: AxiosError | Error, message: string) {
    if (axios.isAxiosError(error) && error.response) {
        console.error(`${message}: HTTP ${error.response.status}`, error.response.data);
    } else {
        console.error(`${message}: ${error.message}`);
    }
}

async function main() {
    const authUrl = 'https://<API Server FQDN>/authenticate';
    const authHeaders: AuthHeaders = {
        Accept: 'application/json',
        'User-Agent': 'YourUserAgent/1.0',
        Authorization: 'Bearer <Your Public API Key Goes Here>',
    };

    const jwt = await authenticate(authUrl, authHeaders);

    if (jwt) {
        const apiUrl = 'https://<API Server FQDN>/api/sales/v1/customer/';
        const apiHeaders =
            {...authHeaders, Authorization: `Bearer ${jwt}`, 'X-Active-Store': '1'};
        const allResults = await fetchAllPages(apiUrl, apiHeaders);
        console.log(`Total results fetched: ${allResults.length}`);
    } else {
        console.log('Failed to authenticate. No data fetched.');
    }
}

main().catch((error) => {
  console.error("An error occurred in the main function:", error);
});
using System.Text.Json;
using System.Net;

class Program
{
    static HttpClient client = new HttpClient();

    static async Task Main(string[] args)
    {

        var handler = new HttpClientHandler();
        if (handler.SupportsAutomaticDecompression)
        {
            handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
        }
        client = new HttpClient(handler);

        string baseUrl = "https://api.toogoerp.net/authenticate";
        var authHeaders = new Dictionary<string, string>
        {
            ["Accept"] = "application/json",
            ["User-Agent"] = "YourUserAgent/1.0",
            ["Authorization"] = "Bearer <Your Public API Key Goes Here>"
        };

        var jwt = await Authenticate(baseUrl, authHeaders);
        if (jwt != null)
        {
            string apiUrl = "https://api.toogoerp.net/api/sales/v1/customer/";
            authHeaders["Authorization"] = $"Bearer {jwt}";
            var allResults = await FetchAllPages(apiUrl, authHeaders);
            Console.WriteLine($"Total results fetched: {allResults.Count}");
        }
        else
        {
            Console.WriteLine("Failed to authenticate. No data fetched.");
        }
    }

    static async Task<string> Authenticate(string url, Dictionary<string, string> headers)
    {
        client.DefaultRequestHeaders.Clear();
        foreach (var header in headers)
        {
            client.DefaultRequestHeaders.Add(header.Key, header.Value);
        }

        var response = await client.PostAsync(url, null);
        if (response.IsSuccessStatusCode)
        {
            var content = await response.Content.ReadAsStringAsync();
            using (var jsonDoc = JsonDocument.Parse(content))
            {
                var root = jsonDoc.RootElement;
                var accessToken = root.GetProperty("access_token").GetString();
                Console.WriteLine("Authentication successful.");
                return accessToken;
            }
        }
        else
        {
            Console.WriteLine($"Failed to authenticate: HTTP {response.StatusCode}");
            return null;
        }
    }

    static async Task<List<JsonElement>> FetchAllPages(string url, Dictionary<string, string> headers)
    {
        List<JsonElement> results = new List<JsonElement>();
        var backoffTime = 1;
        bool fetchNextPage = true;

        while (fetchNextPage)
        {
            client.DefaultRequestHeaders.Clear();
            foreach (var header in headers)
            {
                client.DefaultRequestHeaders.Add(header.Key, header.Value);
            }
            
            // Specify Active Store ID
            client.DefaultRequestHeaders.Add("X-Active-Store", "1");

            try
            {
                var response = await client.GetAsync(url);
                if (response.IsSuccessStatusCode)
                {
                    var content = await response.Content.ReadAsStringAsync();
                    using (var jsonDoc = JsonDocument.Parse(content))
                    {
                        var root = jsonDoc.RootElement;
                        var data = root.GetProperty("results").EnumerateArray();
                        while (data.MoveNext())
                        {
                            results.Add(data.Current);
                        }
                        Console.WriteLine($"Fetched {root.GetProperty("results").GetArrayLength()} results from {url}");
                        url = root.TryGetProperty("next", out var nextUrl) ? nextUrl.GetString() : string.Empty;
                        backoffTime = 1; // Reset backoff time after a successful request
                        fetchNextPage = !string.IsNullOrEmpty(url);
                    }
                }
                else if (response.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
                {
                    Console.WriteLine($"Rate limit exceeded. Retrying in {backoffTime} seconds.");
                    await Task.Delay(backoffTime * 1000);
                    backoffTime *= 2;
                }
                else
                {
                    Console.WriteLine($"Failed to fetch data: HTTP {response.StatusCode}");
                    fetchNextPage = false;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"An error occurred: {ex.Message}");
                fetchNextPage = false;
            }
        }

        return results;
    }
}