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.
- Use
limitto control items per page (default: 20, max: 100) - Use
offsetto skip items and navigate pages - Check
totalto 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โ
| Field | Type | Description |
|---|---|---|
object | string | Always "list" for paginated responses |
data | array | Array of resource objects (charges, customers, etc.) |
limit | integer | Number of items per page (from request) |
offset | integer | Number of items skipped (from request) |
total | integer | Total number of items across all pages |
from | string (ISO 8601) | Start date of the query period (optional) |
to | string (ISO 8601) | End date of the query period (optional) |
order | string | Sort order: "chronological" or "reverse_chronological" |
location | string | API endpoint path |
Paginated Endpointsโ
The following endpoints support pagination:
Core Resourcesโ
| Endpoint | Default Order | Description |
|---|---|---|
GET /charges | Reverse chronological | List all charges |
GET /customers | Reverse chronological | List all customers |
GET /transfers | Reverse chronological | List all transfers |
GET /refunds | Reverse chronological | List all refunds |
GET /transactions | Reverse chronological | List all transactions |
GET /disputes | Reverse chronological | List all disputes |
GET /recipients | Reverse chronological | List all recipients |
GET /events | Reverse chronological | List all events |
GET /schedules | Reverse chronological | List all schedules |
GET /links | Reverse chronological | List all payment links |
Nested Resourcesโ
| Endpoint | Description |
|---|---|
GET /customers/:id/cards | List cards for a customer |
GET /charges/:id/refunds | List refunds for a charge |
GET /customers/:id/charges | List 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:
Navigate to Page 2โ
Skip first 20, get next 20:
curl https://api.omise.co/charges?offset=20&limit=20 \
-u skey_test_5xuy4w91xqz7d1w9u0t:
Navigate to Page 3โ
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`);
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 Page | API Requests for 1000 Items | Recommended For |
|---|---|---|
| 10 | 100 requests | Real-time UI updates |
| 20 (default) | 50 requests | General UI display |
| 50 | 20 requests | Admin dashboards |
| 100 (maximum) | 10 requests | Bulk 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โ
| Parameter | Type | Default | Range | Description |
|---|---|---|---|---|
limit | integer | 20 | 1-100 | Items per page |
offset | integer | 0 | 0-โ | 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);
Related Resourcesโ
Next: Learn about Idempotency to safely retry requests without duplicating operations.