Overview

The GetBill API uses page-based pagination for endpoints that return lists of resources. You can specify the page number and items per page to navigate through large datasets.

Pagination Parameters

All list endpoints support the following query parameters:
page
integer
default:"1"
The page number to retrieve. Pages start at 1.
limit
integer
default:"20"
The number of items to return per page. Maximum is 100.

Response Format

Paginated responses follow this structure:
{
  "error": false,
  "data": [
    {
      "id": "encrypted_id_1",
      // ... resource data
    },
    {
      "id": "encrypted_id_2",
      // ... resource data
    }
  ],
  "pagination": {
    "total": 150,
    "page": 1,
    "limit": 20,
    "pages": 8
  }
}
data
array
Array of resources for the current page
pagination
object
Pagination metadata

Example Usage

Basic Pagination

# Get the first page of debts (default: 20 items)
curl -X GET "https://getbill.io/external-api/v1/debts" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Get the second page with 50 items per page
curl -X GET "https://getbill.io/external-api/v1/debts?page=2&limit=50" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

JavaScript Implementation

class GetBillAPIClient {
  constructor(accessToken) {
    this.accessToken = accessToken;
    this.baseURL = 'https://getbill.io/external-api/v1';
  }

  async getAllDebts() {
    const allDebts = [];
    let page = 1;
    let hasMorePages = true;

    while (hasMorePages) {
      const response = await this.getDebts({ page, limit: 100 });
      
      allDebts.push(...response.data);
      
      hasMorePages = page < response.pagination.pages;
      page++;
    }

    return allDebts;
  }

  async getDebts({ page = 1, limit = 20, ...filters } = {}) {
    const params = new URLSearchParams({
      page: page.toString(),
      limit: limit.toString(),
      ...filters
    });

    const response = await fetch(`${this.baseURL}/debts?${params}`, {
      headers: {
        'Authorization': `Bearer ${this.accessToken}`,
        'Content-Type': 'application/json'
      }
    });

    if (!response.ok) {
      throw new Error(`API request failed: ${response.statusText}`);
    }

    return await response.json();
  }
}

// Usage
const client = new GetBillAPIClient('your_access_token');

// Get first page
const firstPage = await client.getDebts({ page: 1, limit: 50 });

// Get all debts (be careful with large datasets)
const allDebts = await client.getAllDebts();

Python Implementation

import requests
from typing import Iterator, Dict, Any

class GetBillAPI:
    def __init__(self, access_token: str):
        self.access_token = access_token
        self.base_url = "https://getbill.io/external-api/v1"
        self.headers = {
            "Authorization": f"Bearer {access_token}",
            "Content-Type": "application/json"
        }

    def get_debts(self, page: int = 1, limit: int = 20, **filters) -> Dict[str, Any]:
        """Get a single page of debts"""
        params = {"page": page, "limit": limit, **filters}
        
        response = requests.get(
            f"{self.base_url}/debts",
            headers=self.headers,
            params=params
        )
        response.raise_for_status()
        return response.json()

    def iter_all_debts(self, limit: int = 100, **filters) -> Iterator[Dict[str, Any]]:
        """Iterate through all debts across all pages"""
        page = 1
        
        while True:
            response = self.get_debts(page=page, limit=limit, **filters)
            
            for debt in response["data"]:
                yield debt
            
            if page >= response["pagination"]["pages"]:
                break
                
            page += 1

    def get_all_debts(self, **filters) -> list:
        """Get all debts as a list (use with caution for large datasets)"""
        return list(self.iter_all_debts(**filters))

# Usage
api = GetBillAPI("your_access_token")

# Get first page
first_page = api.get_debts(page=1, limit=50)

# Process all debts efficiently
for debt in api.iter_all_debts(status="active"):
    print(f"Processing debt {debt['id']}")

# Get all debts at once (careful with memory)
all_debts = api.get_all_debts(status="active")

Best Practices

Use Appropriate Page Sizes

Choose page sizes based on your use case. Smaller pages (20-50) for UI display, larger pages (100) for bulk processing.

Implement Error Handling

Handle network errors and API failures gracefully, especially in loops processing multiple pages.

Respect Rate Limits

Add delays between requests when processing large datasets to avoid hitting rate limits.

Use Iterators for Large Datasets

For large datasets, use iterators instead of loading all data into memory at once.

Performance Considerations

Memory Usage

When dealing with large datasets:
// ✅ Good: Process items one page at a time
async function processAllDebts() {
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const response = await api.getDebts({ page, limit: 100 });
    
    // Process current page
    for (const debt of response.data) {
      await processDebt(debt);
    }
    
    hasMore = page < response.pagination.pages;
    page++;
  }
}

// ❌ Bad: Load everything into memory
async function processAllDebts() {
  const allDebts = await api.getAllDebts(); // Could be huge!
  
  for (const debt of allDebts) {
    await processDebt(debt);
  }
}

Rate Limiting

Add delays when processing multiple pages:
async function processAllDebtsWithDelay() {
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const response = await api.getDebts({ page, limit: 100 });
    
    // Process current page
    await processDebtsPage(response.data);
    
    hasMore = page < response.pagination.pages;
    
    if (hasMore) {
      // Add delay between requests
      await new Promise(resolve => setTimeout(resolve, 100));
    }
    
    page++;
  }
}

Filtering with Pagination

All pagination parameters can be combined with filtering parameters:
# Get second page of active debts with amounts over €1000
curl -X GET "https://getbill.io/external-api/v1/debts?page=2&limit=50&status=active&amount_min=1000" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Filters are applied before pagination, so the total count reflects the filtered dataset, not all records.

Error Handling

Handle pagination-specific errors:
async function safeGetDebts(page, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      return await api.getDebts({ page });
    } catch (error) {
      if (error.status === 404 && page > 1) {
        // Page doesn't exist, probably reached the end
        return { data: [], pagination: { pages: page - 1 } };
      }
      
      if (i === retries - 1) throw error;
      
      // Wait before retry
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
    }
  }
}

Endpoints Supporting Pagination

The following endpoints support pagination:
  • GET /external-api/v1/debts - List debts
  • GET /external-api/v1/debts/creditor/{creditorId} - List debts by creditor
  • GET /external-api/v1/debts/debtor/{debtorId} - List debts by debtor
  • GET /external-api/v1/debtors - List debtors
  • GET /external-api/v1/creditors - List creditors
  • GET /external-api/v1/followups - List followups
  • GET /external-api/v1/followups/debt/{debtId} - List followups for a debt
  • GET /external-api/v1/reports - List reports
  • GET /external-api/v1/todos - List todos (agreements and disputes)
Each endpoint’s documentation includes details about available filters and sorting options.