Skip to main content
Version: 2019-05-29 (Current)

Pagination

Navigate large result sets efficiently with Omise API pagination. Learn how to use limit and offset parameters to retrieve data in manageable chunks.

Overviewโ€‹

Many Omise API endpoints return lists of resources (charges, customers, transfers, etc.). To keep responses fast and manageable, these endpoints return paginated results. You control how many items to retrieve per request and which page to fetch.

Quick Start
  • Use limit to control items per page (default: 20, max: 100)
  • Use offset to skip items and navigate pages
  • Check total to know how many items exist
  • All list responses have the same structure

Pagination Parametersโ€‹

limitโ€‹

Type: Integer Default: 20 Range: 1-100 Purpose: Number of items to return per request

# Get 50 charges per request
curl https://api.omise.co/charges?limit=50 \
-u skey_test_5xuy4w91xqz7d1w9u0t:

offsetโ€‹

Type: Integer Default: 0 Purpose: Number of items to skip before starting to return results

# Skip first 20 charges, return next 20
curl https://api.omise.co/charges?offset=20&limit=20 \
-u skey_test_5xuy4w91xqz7d1w9u0t:

Combined Exampleโ€‹

# Get items 41-60 (page 3 with 20 items per page)
curl https://api.omise.co/charges?offset=40&limit=20 \
-u skey_test_5xuy4w91xqz7d1w9u0t:

List Response Formatโ€‹

All paginated endpoints return a consistent list structure:

{
"object": "list",
"data": [
{
"object": "charge",
"id": "chrg_test_5xuy4w91xqz7d1w9u0t",
"amount": 100000,
...
},
{
"object": "charge",
"id": "chrg_test_5xuy4w91xqz7d1w9u0a",
"amount": 50000,
...
}
],
"limit": 20,
"offset": 0,
"total": 142,
"from": "2025-01-01T00:00:00Z",
"to": "2025-02-07T23:59:59Z",
"order": "chronological",
"location": "/charges"
}

List Object Fieldsโ€‹

FieldTypeDescription
objectstringAlways "list" for paginated responses
dataarrayArray of resource objects (charges, customers, etc.)
limitintegerNumber of items per page (from request)
offsetintegerNumber of items skipped (from request)
totalintegerTotal number of items across all pages
fromstring (ISO 8601)Start date of the query period (optional)
tostring (ISO 8601)End date of the query period (optional)
orderstringSort order: "chronological" or "reverse_chronological"
locationstringAPI endpoint path

Paginated Endpointsโ€‹

The following endpoints support pagination:

Core Resourcesโ€‹

EndpointDefault OrderDescription
GET /chargesReverse chronologicalList all charges
GET /customersReverse chronologicalList all customers
GET /transfersReverse chronologicalList all transfers
GET /refundsReverse chronologicalList all refunds
GET /transactionsReverse chronologicalList all transactions
GET /disputesReverse chronologicalList all disputes
GET /recipientsReverse chronologicalList all recipients
GET /eventsReverse chronologicalList all events
GET /schedulesReverse chronologicalList all schedules
GET /linksReverse chronologicalList all payment links

Nested Resourcesโ€‹

EndpointDescription
GET /customers/:id/cardsList cards for a customer
GET /charges/:id/refundsList refunds for a charge
GET /customers/:id/chargesList charges for a customer

Basic Pagination Examplesโ€‹

Default Paginationโ€‹

Get the first 20 items (default behavior):

curl https://api.omise.co/charges \
-u skey_test_5xuy4w91xqz7d1w9u0t:

Response:

{
"object": "list",
"data": [...],
"limit": 20,
"offset": 0,
"total": 142
}

Custom Page Sizeโ€‹

Get 50 items per page:

curl https://api.omise.co/charges?limit=50 \
-u skey_test_5xuy4w91xqz7d1w9u0t:

Skip first 20, get next 20:

curl https://api.omise.co/charges?offset=20&limit=20 \
-u skey_test_5xuy4w91xqz7d1w9u0t:

Skip first 40, get next 20:

curl https://api.omise.co/charges?offset=40&limit=20 \
-u skey_test_5xuy4w91xqz7d1w9u0t:

Get Maximum Itemsโ€‹

Get 100 items (maximum allowed):

curl https://api.omise.co/charges?limit=100 \
-u skey_test_5xuy4w91xqz7d1w9u0t:

Pagination Patternsโ€‹

Pattern 1: Iterate All Pagesโ€‹

Fetch all items by iterating through pages:

# Ruby - Fetch all charges
require 'omise'

Omise.api_key = ENV['OMISE_SECRET_KEY']

all_charges = []
offset = 0
limit = 100

loop do
page = Omise::Charge.list(limit: limit, offset: offset)

all_charges.concat(page.data)

# Check if we've retrieved all items
break if offset + page.data.length >= page.total

offset += limit
end

puts "Retrieved #{all_charges.length} total charges"

Pattern 2: Calculate Total Pagesโ€‹

Determine how many pages exist:

# Python - Calculate total pages
import omise
import math

omise.api_secret = os.environ['OMISE_SECRET_KEY']

# Get first page to get total count
first_page = omise.Charge.list(limit=20, offset=0)
total_items = first_page['total']
items_per_page = 20

total_pages = math.ceil(total_items / items_per_page)

print(f"Total items: {total_items}")
print(f"Total pages: {total_pages}")

Pattern 3: Page Navigation UIโ€‹

Build page navigation for a UI:

// Node.js - Page navigation logic
const omise = require('omise')({
secretKey: process.env.OMISE_SECRET_KEY
});

async function getChargesPage(pageNumber, itemsPerPage = 20) {
const offset = (pageNumber - 1) * itemsPerPage;

const result = await omise.charges.list({
limit: itemsPerPage,
offset: offset
});

const totalPages = Math.ceil(result.total / itemsPerPage);

return {
charges: result.data,
pagination: {
currentPage: pageNumber,
totalPages: totalPages,
totalItems: result.total,
itemsPerPage: itemsPerPage,
hasNextPage: pageNumber < totalPages,
hasPrevPage: pageNumber > 1
}
};
}

// Usage: Get page 3
const page3 = await getChargesPage(3, 20);
console.log(page3.pagination);
// {
// currentPage: 3,
// totalPages: 8,
// totalItems: 142,
// itemsPerPage: 20,
// hasNextPage: true,
// hasPrevPage: true
// }

Pattern 4: Cursor-Based Iterationโ€‹

Iterate through all results efficiently:

<?php
// PHP - Iterate all charges

require_once 'vendor/autoload.php';

define('OMISE_SECRET_KEY', getenv('OMISE_SECRET_KEY'));

function getAllCharges($limit = 100) {
$allCharges = [];
$offset = 0;

do {
$page = OmiseCharge::retrieve([
'limit' => $limit,
'offset' => $offset
]);

$allCharges = array_merge($allCharges, $page['data']);

$offset += count($page['data']);
$hasMore = $offset < $page['total'];

} while ($hasMore);

return $allCharges;
}

$charges = getAllCharges();
echo "Retrieved " . count($charges) . " charges\n";

Pattern 5: Lazy Loadingโ€‹

Load more items on demand (infinite scroll):

// JavaScript - Lazy loading for UI
class ChargeLoader {
constructor(itemsPerPage = 20) {
this.itemsPerPage = itemsPerPage;
this.offset = 0;
this.allCharges = [];
this.hasMore = true;
this.total = null;
}

async loadMore() {
if (!this.hasMore) {
return { charges: [], hasMore: false };
}

const response = await fetch('/api/charges', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
limit: this.itemsPerPage,
offset: this.offset
})
});

const result = await response.json();

this.allCharges.push(...result.data);
this.offset += result.data.length;
this.total = result.total;
this.hasMore = this.offset < this.total;

return {
charges: result.data,
hasMore: this.hasMore,
total: this.total,
loaded: this.offset
};
}

reset() {
this.offset = 0;
this.allCharges = [];
this.hasMore = true;
this.total = null;
}
}

// Usage
const loader = new ChargeLoader(20);

// Load first page
const page1 = await loader.loadMore();
console.log(`Loaded ${page1.charges.length} charges`);

// Load second page (user scrolls down)
const page2 = await loader.loadMore();
console.log(`Loaded ${page2.charges.length} more charges`);

Pattern 6: Reverse Pagination (Newest First)โ€‹

Get most recent items first:

// Go - Get newest charges first
package main

import (
"fmt"
"github.com/omise/omise-go"
"github.com/omise/omise-go/operations"
)

func getRecentCharges(limit int) ([]*omise.Charge, error) {
client, _ := omise.NewClient(
os.Getenv("OMISE_PUBLIC_KEY"),
os.Getenv("OMISE_SECRET_KEY"),
)

// Default order is reverse_chronological (newest first)
charges, err := client.Charges().List(&operations.ListCharges{
Limit: limit,
Offset: 0,
})

if err != nil {
return nil, err
}

return charges.Data, nil
}

func main() {
// Get 50 most recent charges
charges, _ := getRecentCharges(50)

for i, charge := range charges {
fmt.Printf("%d. %s - %d %s\n",
i+1,
charge.ID,
charge.Amount,
charge.Currency,
)
}
}

Advanced Pagination Techniquesโ€‹

Combining with Filtersโ€‹

Pagination works with date filters and other parameters:

# Get charges from January 2025, page 2
curl "https://api.omise.co/charges?from=2025-01-01T00:00:00Z&to=2025-01-31T23:59:59Z&limit=20&offset=20" \
-u skey_test_5xuy4w91xqz7d1w9u0t:
# Ruby - Filter + pagination
charges = Omise::Charge.list(
from: '2025-01-01T00:00:00Z',
to: '2025-01-31T23:59:59Z',
limit: 50,
offset: 0
)

Efficient Large Dataset Retrievalโ€‹

For very large datasets, use larger page sizes:

# Python - Efficient bulk retrieval
import omise

omise.api_secret = os.environ['OMISE_SECRET_KEY']

def fetch_all_charges_efficiently():
all_charges = []
offset = 0
limit = 100 # Maximum allowed

while True:
page = omise.Charge.list(limit=limit, offset=offset)

if not page['data']:
break

all_charges.extend(page['data'])

# Check if we got all items
if offset + len(page['data']) >= page['total']:
break

offset += limit

# Optional: Rate limiting
time.sleep(0.1)

return all_charges

charges = fetch_all_charges_efficiently()
print(f"Retrieved {len(charges)} charges")

Progress Trackingโ€‹

Show progress when fetching large datasets:

// Node.js - Progress tracking
async function fetchAllChargesWithProgress(onProgress) {
const limit = 100;
let offset = 0;
let allCharges = [];

// Get first page to know total
const firstPage = await omise.charges.list({ limit, offset });
const total = firstPage.total;

allCharges.push(...firstPage.data);
offset += firstPage.data.length;

onProgress({
loaded: offset,
total: total,
percentage: Math.round((offset / total) * 100)
});

// Fetch remaining pages
while (offset < total) {
const page = await omise.charges.list({ limit, offset });

allCharges.push(...page.data);
offset += page.data.length;

onProgress({
loaded: offset,
total: total,
percentage: Math.round((offset / total) * 100)
});
}

return allCharges;
}

// Usage
const charges = await fetchAllChargesWithProgress((progress) => {
console.log(`Loading: ${progress.percentage}% (${progress.loaded}/${progress.total})`);
});

Parallel Page Fetchingโ€‹

Fetch multiple pages simultaneously (use with caution due to rate limits):

// Node.js - Parallel fetching (advanced)
async function fetchMultiplePagesParallel(startPage, endPage, itemsPerPage = 20) {
const pageNumbers = Array.from(
{ length: endPage - startPage + 1 },
(_, i) => i + startPage
);

const pagePromises = pageNumbers.map(async (pageNum) => {
const offset = (pageNum - 1) * itemsPerPage;
return omise.charges.list({ limit: itemsPerPage, offset });
});

const pages = await Promise.all(pagePromises);

// Flatten all pages into single array
const allCharges = pages.flatMap(page => page.data);

return {
charges: allCharges,
total: pages[0].total
};
}

// Fetch pages 1-5 in parallel
const result = await fetchMultiplePagesParallel(1, 5, 20);
console.log(`Fetched ${result.charges.length} charges`);
Rate Limiting

Parallel requests count toward your rate limit. Use this technique carefully and consider implementing delays between requests.


Handling Edge Casesโ€‹

Empty Resultsโ€‹

When no items match the query:

{
"object": "list",
"data": [],
"limit": 20,
"offset": 0,
"total": 0,
"location": "/charges"
}
// Check for empty results
const charges = await omise.charges.list({ limit: 20 });

if (charges.data.length === 0) {
console.log('No charges found');
} else {
console.log(`Found ${charges.data.length} charges`);
}

Offset Beyond Totalโ€‹

Requesting an offset beyond available items:

# Total is 50, but requesting offset 100
curl "https://api.omise.co/charges?offset=100&limit=20" \
-u skey_test_...:

Response:

{
"object": "list",
"data": [],
"limit": 20,
"offset": 100,
"total": 50
}

Last Page Partial Resultsโ€‹

Last page may have fewer items than limit:

# Total is 145, requesting items 141-160
curl "https://api.omise.co/charges?offset=140&limit=20" \
-u skey_test_...:

Response:

{
"object": "list",
"data": [
{ "object": "charge", "id": "chrg_141" },
{ "object": "charge", "id": "chrg_142" },
{ "object": "charge", "id": "chrg_143" },
{ "object": "charge", "id": "chrg_144" },
{ "object": "charge", "id": "chrg_145" }
],
"limit": 20,
"offset": 140,
"total": 145
}
// Detect last page
const page = await omise.charges.list({ offset: 140, limit: 20 });

const isLastPage = page.offset + page.data.length >= page.total;
console.log(`Is last page: ${isLastPage}`); // true

Dynamic Total Changesโ€‹

The total count can change between requests if new items are created:

// First request
const page1 = await omise.charges.list({ limit: 20, offset: 0 });
console.log(`Total: ${page1.total}`); // 100

// New charges created...

// Second request
const page2 = await omise.charges.list({ limit: 20, offset: 20 });
console.log(`Total: ${page2.total}`); // 105 (changed!)

// Handle this by checking total on each request

Best Practicesโ€‹

1. Use Appropriate Page Sizesโ€‹

// โœ… Good - Choose size based on use case

// For UI display (don't overwhelm users)
const charges = await omise.charges.list({ limit: 20 });

// For bulk processing (maximize efficiency)
const chargesForExport = await omise.charges.list({ limit: 100 });

// For real-time updates (minimize latency)
const recentCharges = await omise.charges.list({ limit: 10 });

2. Cache Results When Possibleโ€‹

# โœ… Good - Cache paginated results
require 'redis'

redis = Redis.new

def get_charges_page(page_num, cache_ttl = 300)
cache_key = "charges:page:#{page_num}"

# Try cache first
cached = redis.get(cache_key)
return JSON.parse(cached) if cached

# Fetch from API
offset = (page_num - 1) * 20
charges = Omise::Charge.list(limit: 20, offset: offset)

# Cache for 5 minutes
redis.setex(cache_key, cache_ttl, charges.to_json)

charges
end

3. Handle Pagination Errorsโ€‹

# โœ… Good - Handle pagination errors
import omise

def safe_list_charges(offset=0, limit=20, max_retries=3):
for attempt in range(max_retries):
try:
return omise.Charge.list(offset=offset, limit=limit)

except omise.errors.BaseError as e:
if attempt == max_retries - 1:
raise

if e.http_status >= 500:
# Server error - retry
time.sleep(2 ** attempt)
continue
else:
# Client error - don't retry
raise

charges = safe_list_charges(offset=0, limit=50)

4. Validate Pagination Parametersโ€‹

<?php
// โœ… Good - Validate parameters

function getChargesPage($pageNum, $itemsPerPage) {
// Validate page number
if ($pageNum < 1) {
throw new InvalidArgumentException('Page number must be >= 1');
}

// Validate items per page
if ($itemsPerPage < 1 || $itemsPerPage > 100) {
throw new InvalidArgumentException('Items per page must be 1-100');
}

$offset = ($pageNum - 1) * $itemsPerPage;

return OmiseCharge::retrieve([
'limit' => $itemsPerPage,
'offset' => $offset
]);
}

5. Show Pagination State to Usersโ€‹

// โœ… Good - Clear pagination UI
function renderPagination(currentPage, totalPages) {
return `
<div class="pagination">
<button ${currentPage === 1 ? 'disabled' : ''}>
Previous
</button>

<span>Page ${currentPage} of ${totalPages}</span>

<button ${currentPage === totalPages ? 'disabled' : ''}>
Next
</button>

<span class="total-info">
Showing ${(currentPage - 1) * 20 + 1}-${Math.min(currentPage * 20, totalItems)}
of ${totalItems} items
</span>
</div>
`;
}

6. Optimize for Large Datasetsโ€‹

# โœ… Good - Stream large datasets
def export_all_charges_to_csv
CSV.open('charges.csv', 'wb') do |csv|
csv << ['ID', 'Amount', 'Currency', 'Status', 'Created']

offset = 0
limit = 100

loop do
page = Omise::Charge.list(limit: limit, offset: offset)

page.data.each do |charge|
csv << [
charge.id,
charge.amount,
charge.currency,
charge.status,
charge.created_at
]
end

break if offset + page.data.length >= page.total

offset += limit

# Rate limiting
sleep(0.5)
end
end
end

7. Calculate Page Numbers Correctlyโ€‹

// โœ… Good - Correct page calculations

function calculatePageInfo(offset, limit, total) {
const currentPage = Math.floor(offset / limit) + 1;
const totalPages = Math.ceil(total / limit);
const itemsOnPage = Math.min(limit, total - offset);

return {
currentPage,
totalPages,
itemsOnPage,
firstItemNum: offset + 1,
lastItemNum: offset + itemsOnPage,
hasNextPage: offset + limit < total,
hasPrevPage: offset > 0
};
}

// Example
const info = calculatePageInfo(40, 20, 142);
console.log(info);
// {
// currentPage: 3,
// totalPages: 8,
// itemsOnPage: 20,
// firstItemNum: 41,
// lastItemNum: 60,
// hasNextPage: true,
// hasPrevPage: true
// }

Performance Considerationsโ€‹

Request Efficiencyโ€‹

Items Per PageAPI Requests for 1000 ItemsRecommended For
10100 requestsReal-time UI updates
20 (default)50 requestsGeneral UI display
5020 requestsAdmin dashboards
100 (maximum)10 requestsBulk processing, exports

Memory Managementโ€‹

# โœ… Good - Process in chunks to manage memory
def process_all_charges():
offset = 0
limit = 100

while True:
# Fetch page
page = omise.Charge.list(limit=limit, offset=offset)

if not page['data']:
break

# Process this page
for charge in page['data']:
process_charge(charge)

# Don't keep all charges in memory
offset += len(page['data'])

if offset >= page['total']:
break

# โŒ Bad - Loads everything into memory
def process_all_charges_bad():
all_charges = []
offset = 0

# This could use gigabytes of RAM for large datasets
while True:
page = omise.Charge.list(limit=100, offset=offset)
all_charges.extend(page['data'])
offset += 100
if offset >= page['total']:
break

for charge in all_charges:
process_charge(charge)

Rate Limiting Awarenessโ€‹

// โœ… Good - Respect rate limits
async function fetchAllChargesSafely() {
const charges = [];
let offset = 0;
const limit = 100;

while (true) {
try {
const page = await omise.charges.list({ limit, offset });

charges.push(...page.data);

if (offset + page.data.length >= page.total) {
break;
}

offset += limit;

// Small delay to avoid rate limits
await new Promise(resolve => setTimeout(resolve, 100));

} catch (error) {
if (error.statusCode === 429) {
// Rate limited - wait longer
await new Promise(resolve => setTimeout(resolve, 5000));
continue;
}
throw error;
}
}

return charges;
}

Testing Paginationโ€‹

Test Edge Casesโ€‹

# Test pagination edge cases
describe 'Charge Pagination' do
it 'handles first page' do
charges = Omise::Charge.list(limit: 20, offset: 0)
expect(charges.offset).to eq(0)
expect(charges.data.length).to be <= 20
end

it 'handles last page' do
first_page = Omise::Charge.list(limit: 20, offset: 0)
total = first_page.total

# Calculate last page offset
last_offset = (total / 20) * 20

last_page = Omise::Charge.list(limit: 20, offset: last_offset)
expect(last_page.data.length).to be <= 20
end

it 'handles empty results' do
# Filter that returns no results
charges = Omise::Charge.list(
from: '2030-01-01T00:00:00Z',
to: '2030-01-02T00:00:00Z'
)
expect(charges.data).to be_empty
expect(charges.total).to eq(0)
end

it 'handles offset beyond total' do
charges = Omise::Charge.list(limit: 20, offset: 999999)
expect(charges.data).to be_empty
end
end

Common Pitfallsโ€‹

โŒ Don't: Hardcode Offset Valuesโ€‹

// โŒ Bad - Assumes static data
const page1 = await omise.charges.list({ offset: 0, limit: 20 });
const page2 = await omise.charges.list({ offset: 20, limit: 20 });
// If items were added/removed, page2 might have duplicates or gaps
// โœ… Good - Calculate offset dynamically
let offset = 0;
const limit = 20;

const page1 = await omise.charges.list({ limit, offset });
offset += page1.data.length; // Dynamic offset

const page2 = await omise.charges.list({ limit, offset });

โŒ Don't: Ignore Total Countโ€‹

# โŒ Bad - Doesn't check if more pages exist
offset = 0
while True:
page = omise.Charge.list(limit=20, offset=offset)
process(page['data'])
offset += 20
# This loops forever if you don't check total!
# โœ… Good - Check total
offset = 0
limit = 20

while True:
page = omise.Charge.list(limit=limit, offset=offset)
process(page['data'])

offset += len(page['data'])
if offset >= page['total']:
break

โŒ Don't: Use Limit > 100โ€‹

# โŒ Bad - Limit too high
curl "https://api.omise.co/charges?limit=500" \
-u skey_test_...:
# Error: limit must be <= 100
# โœ… Good - Use maximum of 100
curl "https://api.omise.co/charges?limit=100" \
-u skey_test_...:

Quick Referenceโ€‹

Pagination Parametersโ€‹

ParameterTypeDefaultRangeDescription
limitinteger201-100Items per page
offsetinteger00-โˆžItems to skip

List Response Fieldsโ€‹

{
"object": "list",
"data": [...], // Array of items
"limit": 20, // Items per page
"offset": 0, // Items skipped
"total": 142, // Total items
"from": "...", // Start date (optional)
"to": "...", // End date (optional)
"order": "...", // Sort order
"location": "..." // Endpoint path
}

Calculate Pagesโ€‹

// Current page number
const currentPage = Math.floor(offset / limit) + 1;

// Total pages
const totalPages = Math.ceil(total / limit);

// Has next page
const hasNextPage = offset + limit < total;

// Has previous page
const hasPrevPage = offset > 0;

// Next page offset
const nextOffset = offset + limit;

// Previous page offset
const prevOffset = Math.max(0, offset - limit);


Next: Learn about Idempotency to safely retry requests without duplicating operations.