hasMore to retrieve complete datasets. Use REST for real-time CRUD under 10K records; switch to FBDI for bulk imports and BICC for bulk extracts.limit=500 does NOT return 500 records -- it returns 499 AND reports totalCount as 499, hiding the true total count. Always use limit=499 and rely on the hasMore flag.Oracle Fusion Cloud Applications (also marketed as Oracle Cloud ERP, Oracle Cloud SCM, Oracle Cloud HCM, and Oracle CX) exposes a comprehensive REST API surface for real-time integration with business objects across all pillars. The REST API is built on the Oracle ADF Business Components framework and has evolved through multiple framework versions (v1 through v4). This card covers the REST API surface specifically -- not SOAP, BICC, or FBDI. All Oracle Fusion Cloud editions share the same REST API capabilities.
| Property | Value |
|---|---|
| Vendor | Oracle |
| System | Oracle Fusion Cloud Applications (ERP, SCM, HCM, CX) |
| API Surface | REST (JSON over HTTPS) |
| Current API Version | Framework v4 (available since 24B release) |
| Editions Covered | All editions (no API differences by edition) |
| Deployment | Cloud (SaaS only) |
| API Docs | Oracle Fusion Cloud REST API Reference |
| Status | GA (strategic direction -- SOAP deprecated for new development) |
Oracle Fusion Cloud offers multiple API surfaces for different integration patterns. REST is the primary modern surface, but understanding when to use alternatives is critical. [src5, src6]
| API Surface | Protocol | Best For | Max Records/Request | Rate Limit | Real-time? | Bulk? |
|---|---|---|---|---|---|---|
| REST API (fscmRestApi) | HTTPS/JSON | Real-time CRUD, <10K records | 499 per GET page | ~5,000 calls/hr/user | Yes | No |
| SOAP Web Services | HTTPS/XML | Legacy integrations, metadata ops | Varies by service | Shared with REST | Yes | No |
| FBDI (File-Based Data Import) | CSV in ZIP via REST | Bulk inbound imports, data migration | 100,000 per import job | 5 concurrent imports | No | Yes |
| BICC (BI Cloud Connector) | Bulk extract via UCM | Large outbound data extraction | Millions (full/incremental) | Scheduled | No | Yes |
| BIP (BI Publisher) | SOAP/REST | Report-based data extraction | Report-dependent | Report queue limits | No | Partial |
| Business Events | Event subscription | Event-driven integration via OIC | N/A (push) | Event queue limits | Yes | N/A |
| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| Max records per GET response | 499 | REST API GET (all resources) | Hard cap. Setting limit > 499 silently returns 499 and misreports totalCount |
| Default page size | 25 | REST API GET (no limit param) | Override with ?limit=499 for efficiency |
| Max POST records (recommended) | 500 | REST API POST/batch creates | Performance degrades significantly above this threshold |
| Max request payload size | ~10 MB | REST API POST/PATCH | Larger payloads may timeout or be rejected by WAF |
| Max FBDI ZIP file size | 250 MB | File-Based Data Import | Individual CSV files within ZIP can be up to 1 GB |
| Max FBDI records per import | 100,000 | File-Based Data Import | Split larger datasets into multiple import jobs |
| Max concurrent FBDI imports | 5 | File-Based Data Import | Additional imports queue behind running jobs |
| Limit Type | Value | Window | Edition Differences |
|---|---|---|---|
| API calls per user | ~5,000 | Per hour (rolling) | No edition differences -- same for all Fusion Cloud tenants |
| Total tenant API calls | Fair-use throttling | Rolling window | Oracle reserves right to throttle tenants impacting shared infrastructure |
| FBDI concurrent imports | 5 | Per tenant | Shared across all users and integration flows |
| BIP concurrent reports | Queue-based | Per tenant | Long-running reports can block the queue |
| BICC extract jobs | Scheduled | Per tenant | Typically 1-2 concurrent extract streams |
Oracle Fusion Cloud REST APIs use a global Oracle Web Services Manager (OWSM) security policy called Multi Token Over SSL. [src4]
| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| Basic Auth over SSL | Testing, simple integrations | Per request | No | Username:password in base64; not recommended for production |
| OAuth 2.0 (Client Credentials) | Server-to-server production integrations | Configurable (~1h) | Yes | Recommended; requires Oracle IAM confidential app setup |
| OAuth 2.0 (JWT Bearer) | Automated integrations, service accounts | JWT-defined | New JWT per request | Requires certificate registration in Oracle IAM |
| SAML 2.0 Bearer | SSO federation, cross-cloud identity | Assertion validity | No | Token passed in HTTP Authorization header |
| OAuth 2.0 (3-legged) | User-context operations, delegated access | Access: ~1h, Refresh: until revoked | Yes | Standard authorization code flow |
limit parameter value. Setting limit=500 or higher silently returns 499 and incorrectly reports totalCount as 499.hasMore/count metadata. Integration code written for v1/v2 responses will break on v3+ payloads.LastUpdateDate filters.START -- User needs to integrate with Oracle Fusion Cloud
+-- What's the data flow direction?
| +-- OUTBOUND (reading from Fusion)?
| | +-- How many records per extraction?
| | | +-- < 1,000 records --> REST API with limit=499 + offset pagination
| | | +-- 1,000 - 10,000 records --> REST API pagination loop (2-20 API calls)
| | | +-- 10,000 - 100,000 records --> Consider BIP reports or BICC incremental
| | | +-- > 100,000 records --> BICC (mandatory for large extracts)
| | +-- Need real-time or scheduled?
| | +-- Real-time --> REST API with LastUpdateDate filter for delta
| | +-- Scheduled --> BICC incremental extract
| +-- INBOUND (writing to Fusion)?
| | +-- How many records per load?
| | | +-- < 500 records --> REST API POST/PATCH
| | | +-- 500 - 100,000 records --> FBDI (file-based import via REST upload)
| | | +-- > 100,000 records --> FBDI with split files
| | +-- Need immediate confirmation?
| | +-- YES --> REST API (synchronous response)
| | +-- NO --> FBDI (asynchronous, poll for status)
| +-- EVENT-DRIVEN (react to changes in Fusion)?
| +-- Business Events available for your object?
| | +-- YES --> Subscribe via OIC ERP Cloud Adapter
| | +-- NO --> REST API polling with LastUpdateDate filter
| +-- Need guaranteed delivery?
| +-- YES --> OIC + dead letter queue
| +-- NO --> REST polling with retry
+-- Error tolerance?
+-- Zero-loss required --> Idempotent design + FBDI (built-in per-record error reporting)
+-- Best-effort --> REST with retry on 429/5xx
| Operation | Method | Endpoint Pattern | Payload | Notes |
|---|---|---|---|---|
| List resources | GET | /{apiBase}/resources/{version}/{resource} | N/A | Returns max 499 records; use offset for pagination |
| Get single record | GET | /{apiBase}/resources/{version}/{resource}/{id} | N/A | Returns full record with expand support |
| Create record | POST | /{apiBase}/resources/{version}/{resource} | JSON | Returns created record with generated ID |
| Update record | PATCH | /{apiBase}/resources/{version}/{resource}/{id} | JSON | Partial update; only send changed fields |
| Replace record | PUT | /{apiBase}/resources/{version}/{resource}/{id} | JSON | Full replacement; all fields required |
| Delete record | DELETE | /{apiBase}/resources/{version}/{resource}/{id} | N/A | Permanent; no soft-delete via API |
| Query with filter | GET | /{apiBase}/resources/{version}/{resource}?q=expr | N/A | v1: q=field=value; v2+: rowmatch |
| Expand children | GET | /{apiBase}/resources/{version}/{resource}?expand={child} | N/A | Fetches child resources inline |
| Select fields | GET | /{apiBase}/resources/{version}/{resource}?fields={f1,f2} | N/A | Reduces payload size; improves performance |
| Pillar | API Base Path | Example |
|---|---|---|
| Financials/SCM | /fscmRestApi | https://{host}/fscmRestApi/resources/11.13.18.05/invoices |
| HCM | /hcmRestApi | https://{host}/hcmRestApi/resources/11.13.18.05/workers |
| CRM/CX | /crmRestApi | https://{host}/crmRestApi/resources/11.13.18.05/accounts |
| Common | /commonRestApi | https://{host}/commonRestApi/resources/11.13.18.05/users |
| Parameter | Purpose | Example | Framework Version |
|---|---|---|---|
limit | Records per page (max 499) | ?limit=499 | All |
offset | Skip N records for pagination | ?offset=499 | All |
q | Filter expression | ?q=Status=APPROVED | All (syntax varies) |
fields | Select specific fields | ?fields=InvoiceId,InvoiceNumber | All |
expand | Include child resources | ?expand=invoiceLines | All |
onlyData | Exclude metadata wrapper | ?onlyData=true | v2+ |
orderBy | Sort results | ?orderBy=CreationDate:desc | v2+ |
totalResults | Include total count | ?totalResults=true | v2+ |
finder | Use predefined query finder | ?finder=FindByStatus;Status=OPEN | All |
Set up OAuth 2.0 client credentials flow with Oracle IAM for production integrations. [src4]
# OAuth 2.0 Client Credentials -- obtain access token
curl -s -X POST \
"https://YOUR_IDCS_DOMAIN.identity.oraclecloud.com/oauth2/v1/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "CLIENT_ID:CLIENT_SECRET" \
-d "grant_type=client_credentials&scope=urn:opc:resource:consumer::all"
Verify: Response contains access_token and expires_in fields
Fetch records using the 499-record pagination pattern. Always use hasMore to control the loop. [src2, src3]
# Fetch first page of invoices (499 records max)
curl -s -X GET \
"https://YOUR_HOST/fscmRestApi/resources/11.13.18.05/invoices?limit=499&offset=0&fields=InvoiceId,InvoiceNumber,InvoiceAmount" \
-H "Authorization: Bearer $ACCESS_TOKEN"
Verify: Check count and hasMore fields in response
Loop through all pages using offset increments until hasMore is false. [src3]
all_records = []
offset = 0
while True:
params = {"limit": 499, "offset": offset, "onlyData": "true"}
resp = requests.get(url, headers=headers, params=params)
data = resp.json()
all_records.extend(data.get("items", []))
if not data.get("hasMore", False):
break
offset += 499
Verify: len(all_records) matches total record count in Oracle Fusion
Reduce payload size by selecting only needed fields and expanding child resources inline. [src1]
# Fetch invoice with expanded lines and selected fields
curl -s -X GET \
"https://YOUR_HOST/fscmRestApi/resources/11.13.18.05/invoices/12345?\
fields=InvoiceId,InvoiceNumber&expand=invoiceLines(fields=LineNumber,LineAmount)" \
-H "Authorization: Bearer $ACCESS_TOKEN"
Verify: Response includes invoiceLines nested under the invoice
Insert a new business object. [src7]
# Create a new invoice
curl -s -X POST \
"https://YOUR_HOST/fscmRestApi/resources/11.13.18.05/invoices" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"InvoiceNumber":"INV-2026-001","InvoiceAmount":15000.00,"InvoiceCurrency":"USD"}'
Verify: HTTP status 201 -- response body contains system-generated InvoiceId
Implement exponential backoff for 429 and 5xx responses. [src7]
for attempt in range(max_retries):
resp = requests.get(url, headers=headers, timeout=60)
if resp.status_code == 200:
return resp.json()
elif resp.status_code == 429:
time.sleep(min(2 ** attempt * 2, 120))
continue
elif resp.status_code >= 500:
time.sleep(min(2 ** attempt * 3, 180))
continue
Verify: Test with invalid endpoint returns 404; invalid auth returns 401
# Input: Oracle Fusion Cloud credentials, business unit filter
# Output: Complete list of approved invoices as Python dicts
import requests, time
class OracleFusionClient:
def __init__(self, host, client_id, client_secret, idcs_domain):
self.host = host
self.token_url = f"https://{idcs_domain}.identity.oraclecloud.com/oauth2/v1/token"
self.auth = (client_id, client_secret)
self.access_token = None
def authenticate(self):
resp = requests.post(self.token_url, auth=self.auth,
data={"grant_type": "client_credentials",
"scope": "urn:opc:resource:consumer::all"}, timeout=30)
resp.raise_for_status()
self.access_token = resp.json()["access_token"]
def get_all_records(self, resource, query=None, fields=None):
url = f"https://{self.host}/fscmRestApi/resources/11.13.18.05/{resource}"
headers = {"Authorization": f"Bearer {self.access_token}"}
all_items, offset = [], 0
while True:
params = {"limit": 499, "offset": offset, "onlyData": "true"}
if query: params["q"] = query
if fields: params["fields"] = fields
resp = requests.get(url, headers=headers, params=params, timeout=60)
if resp.status_code == 429:
time.sleep(30); continue
resp.raise_for_status()
data = resp.json()
all_items.extend(data.get("items", []))
if not data.get("hasMore", False): break
offset += 499
return all_items
// Input: Oracle Fusion Cloud credentials, resource name
// Output: Records with expanded child resources
const https = require('https');
async function fetchWithExpand(host, token, resource, id, expandChild, fields) {
let url = `https://${host}/fscmRestApi/resources/11.13.18.05/${resource}/${id}`;
const params = new URLSearchParams();
if (fields) params.set('fields', fields);
if (expandChild) params.set('expand', expandChild);
params.set('onlyData', 'true');
url += '?' + params.toString();
return new Promise((resolve, reject) => {
const req = https.get(url, {
headers: { 'Authorization': `Bearer ${token}` }
}, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
if (res.statusCode === 200) resolve(JSON.parse(data));
else reject(new Error(`HTTP ${res.statusCode}: ${data}`));
});
});
req.on('error', reject);
});
}
# Input: Access token, Oracle Fusion Cloud host
# Output: JSON responses for common API operations
# Discover available resources
curl -s "https://YOUR_HOST/fscmRestApi/resources/11.13.18.05" \
-H "Authorization: Bearer $TOKEN" | python3 -m json.tool | head -50
# Get resource metadata
curl -s "https://YOUR_HOST/fscmRestApi/resources/11.13.18.05/invoices/describe" \
-H "Authorization: Bearer $TOKEN"
# Pagination test -- check hasMore behavior
curl -s "https://YOUR_HOST/fscmRestApi/resources/11.13.18.05/invoices?\
limit=2&totalResults=true" -H "Authorization: Bearer $TOKEN"
| Oracle Fusion Type | JSON Type | API Format | Transform Notes | Gotcha |
|---|---|---|---|---|
| DATE | String | YYYY-MM-DD | No timezone info | Oracle stores as UTC internally; display depends on user timezone |
| DATETIME/TIMESTAMP | String | ISO 8601 with TZ | Always include timezone offset | Avoid ambiguity by including +00:00 |
| NUMBER | Number | Decimal | No rounding | Currency amounts may have 2-6 decimal places |
| VARCHAR2 | String | UTF-8 | Direct mapping | Max length varies by field; truncation may be silent |
| CLOB | String | UTF-8 | Long text fields | May not be available via fields parameter |
| LOB/BLOB | String | Base64-encoded | Binary data | Must decode; check Content-Transfer-Encoding |
| Flexfield (DFF) | Object | Nested JSON | Descriptive flexfield | Segment names are configurable per tenant; never hardcode |
| Lookup | String | Code value | Lookup code, not meaning | API returns internal code; use expand=lookups for both |
YYYY-MM-DD format with no timezone. The actual timezone depends on the integration user's profile. Always configure the integration user with UTC timezone. [src7]/describe endpoint to discover segment names dynamically. [src1]expand=lookups to get both. [src1]| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| 400 | Bad Request | Malformed JSON, invalid field name, wrong query syntax | Check request body; verify field names via /describe; check framework version for query syntax |
| 401 | Unauthorized | Expired token, invalid credentials | Re-authenticate; check token expiry; verify OAuth client configuration |
| 403 | Forbidden | Missing role or data security policy | Assign correct duty/job roles to integration user; check data security policies |
| 404 | Not Found | Wrong resource name, invalid record ID, wrong API version | Verify resource name at /{apiBase}/resources/{version} |
| 429 | Too Many Requests | Rate limit exceeded (~5,000 calls/hr/user) | Implement exponential backoff; reduce call frequency; batch operations |
| 500 | Internal Server Error | Oracle server-side issue, complex query timeout | Simplify query; reduce expand depth; retry after delay |
| 503 | Service Unavailable | Maintenance window, tenant restart | Wait and retry; check Oracle Cloud status page |
limit=500 returns 499 records AND reports totalCount=499, hiding that more records exist. Fix: Always use limit=499 and loop on hasMore=true. [src2, src3]Check framework version in API URL and parse responses accordingly. [src1]Set all integration users to UTC timezone in Oracle Fusion user preferences. [src7]Use the /describe endpoint to validate flexfield segments before processing.Cache the access token and reuse until near expiry. [src4]Always check the FBDI import error report after each job. [src5]# BAD -- totalCount is capped at 499 when limit >= 500
response = requests.get(f"{url}?limit=500")
data = response.json()
total = data["totalResults"] # Returns 499 (WRONG!)
# Developer thinks there are only 499 records
# GOOD -- loop on hasMore, always use limit=499
all_records = []
offset = 0
while True:
response = requests.get(f"{url}?limit=499&offset={offset}")
data = response.json()
all_records.extend(data["items"])
if not data.get("hasMore", False):
break
offset += 499
# BAD -- extracting 500K records via REST API
# At 499 records per page = 1,000+ API calls
# At ~5,000 calls/hr limit, this takes hours
for offset in range(0, 500000, 499):
resp = requests.get(f"{url}?limit=499&offset={offset}")
records.extend(resp.json()["items"])
# GOOD -- use BICC for bulk extraction
# 1. Configure BICC extract in Oracle Fusion
# 2. Schedule incremental extract
# 3. Retrieve files from UCM
# Use REST only for real-time lookups on individual records
resp = requests.get(f"{url}/{record_id}?fields=Id,Status,Amount")
# BAD -- requests a new token for every call
def get_invoice(invoice_id):
token = get_new_oauth_token() # New token EVERY call!
return requests.get(f"{url}/{invoice_id}",
headers={"Authorization": f"Bearer {token}"})
# GOOD -- cache token and reuse
class TokenManager:
def get_token(self):
if time.time() < self._expires_at - 60: # 60s buffer
return self._token
resp = requests.post(self.token_url, auth=self.auth,
data={"grant_type": "client_credentials"})
data = resp.json()
self._token = data["access_token"]
self._expires_at = time.time() + data["expires_in"]
return self._token
fields, the API returns ALL attributes including LOB fields and flexfields, dramatically increasing response size. [src1]/describe endpoint. [src6]# Test authentication (Basic Auth for quick testing)
curl -s -o /dev/null -w "%{http_code}" \
"https://YOUR_HOST/fscmRestApi/resources/11.13.18.05" \
-u "USERNAME:PASSWORD"
# Expected: 200 (authenticated) or 401 (bad credentials)
# Discover available resources
curl -s "https://YOUR_HOST/fscmRestApi/resources/11.13.18.05" \
-H "Authorization: Bearer $TOKEN" | python3 -m json.tool | head -20
# Get resource metadata (field names and types)
curl -s "https://YOUR_HOST/fscmRestApi/resources/11.13.18.05/invoices/describe" \
-H "Authorization: Bearer $TOKEN"
# Test pagination -- verify hasMore behavior
curl -s "https://YOUR_HOST/fscmRestApi/resources/11.13.18.05/invoices?limit=2&totalResults=true" \
-H "Authorization: Bearer $TOKEN"
# Check OAuth token validity
curl -s -o /dev/null -w "%{http_code}" \
"https://YOUR_HOST/fscmRestApi/resources/11.13.18.05" \
-H "Authorization: Bearer $TOKEN"
| Framework Version | Available Since | Status | Key Changes | Migration Notes |
|---|---|---|---|---|
| v4 | 24B (2024 Q2) | Current | Latest query enhancements, improved performance | Recommended for new integrations |
| v3 | 23D (2023 Q4) | Supported | Nested children as collections with hasMore/count | Breaking: child resources change from arrays to collection objects |
| v2 | 22C (2022 Q3) | Supported | rowmatch expressions; onlyData, orderBy, totalResults | Non-breaking; adds new query syntax |
| v1 | Initial | Supported | Basic q=field=value; expand, fields, limit, offset | Original framework; still works but limited |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Real-time individual record CRUD (<500 records) | Bulk data extraction (>10K records) | BICC for large outbound extracts |
| Interactive lookups with field filtering and expand | Bulk data import (>500 records) | FBDI for inbound bulk loads |
| Event-driven polling with LastUpdateDate filter | Complex reporting/analytics queries | BIP (BI Publisher) reports |
| Lightweight validation lookups | Full data migration (initial load) | FBDI + HCM Data Loader |
| Webhook alternative via polling | Need guaranteed exactly-once delivery | OIC with Business Events subscription |