"The 6 Pagination Bugs That Eat Oracle Fusion REST API Integrations"
Every collection GET against an Oracle Fusion REST API is paginated whether you ask for it or not. Call GET /workers, treat the response as "all workers," and you silently got the first 25 rows - nothing warns you that thousands more exist.
Here are the six pagination bugs I keep seeing in Fusion integrations, and the loop that avoids all of them. Replace acme.fa.us2.oraclecloud.com with your pod; all sample data is anonymized.
The response wrapper:
{
"items": [ ... ],
"count": 25,
"hasMore": true,
"limit": 25,
"offset": 0
}
countis this page's size, not the total.hasMoreis your loop condition.limitis what the server actually used - it can override what you asked for.offsetis 0-based.
The Six Bugs
- First 25 rows only. No
limitsent, default applied,hasMoreignored. The classic. - Off-by-one offset.
offsetis 0-based; a 1-based assumption (a real bug in identity-management connectors) duplicates or drops one record at every page boundary. - Silent limit override. The cap is 500 on most resources. Ask for
limit=1000, get 500, increment offset by 1000 โ every second page skipped. Always increment by the response'slimit. - Unstable ordering. No
orderBymeans rows can shift between calls - nightly syncs "lose" records nondeterministically. Always sort on a stable unique key (PersonId,InvoiceId). totalResultson every page. It forces an extra count query per request. Fetch it once withlimit=1, then run the loop without it.- Restarting after a 429. A 500-per-page loop over a big resource is thousands of calls. Back off and resume from the saved
offset; don't start over.
The Loop That Avoids All Six
BASE='https://acme.fa.us2.oraclecloud.com/fscmRestApi/resources/11.13.18.05/invoices'
LIMIT=500
OFFSET=0
while :; do
PAGE=$(curl -s -u 'integration.user@example.com:YourPassword' \
"$BASE?limit=$LIMIT&offset=$OFFSET&onlyData=true&orderBy=InvoiceId")
echo "$PAGE" | jq -r '.items[].InvoiceNumber'
[ "$(echo "$PAGE" | jq '.hasMore')" = "true" ] || break
OFFSET=$((OFFSET + $(echo "$PAGE" | jq '.limit')))
done
onlyData=true strips per-item HATEOAS links and shrinks payloads dramatically - always use it when iterating.
Filter Before You Paginate
limit/offset compose with q and finders. A q filter that cuts 48,000 rows to 3,000 saves you 90 pages before pagination starts:
curl -u 'integration.user@example.com:YourPassword' \
'https://acme.fa.us2.oraclecloud.com/hcmRestApi/resources/11.13.18.05/workers?q=assignments.AssignmentStatusType=%27ACTIVE%27&limit=500&offset=0&onlyData=true&orderBy=PersonId'
Which fields are filterable is endpoint-specific - /workers alone has 307 queryable fields, and the free field-level reference at opalapi.dev/apis lists q fields, finders, and child resources for the top HCM and FSCM endpoints.
Expanded Children Paginate Too
From REST framework v3, an expanded child returns a collection wrapper with its own hasMore - and any child with more than 500 items isn't expanded at all (you get a links entry instead). For large children (assignments across history), skip expand and paginate the child URI directly:
curl -u 'integration.user@example.com:YourPassword' \
'https://acme.fa.us2.oraclecloud.com/hcmRestApi/resources/11.13.18.05/workers/00020000000EACED.../child/assignments?limit=100&offset=0&onlyData=true'
Originally published on the OPAL blog - the full version covers totalResults costs, hasMore-vs-row-security gotchas, and framework-version differences. OPAL is a free offline desktop explorer for 59,000+ Oracle Fusion endpoints: opalapi.dev.
Comments
No comments yet. Start the discussion.