Oracle ERP Cloud (Fusion) REST API Capabilities
What are Oracle ERP Cloud (Fusion) REST API capabilities - 499-record pagination, expand vs fields?
TL;DR
- Bottom line: Oracle Fusion Cloud REST APIs support full CRUD on hundreds of ERP resources with JSON payloads, but are limited to 499 records per request and best suited for real-time, low-volume integrations — use FBDI for bulk operations. [src1]
- Key limit: Maximum 500 records per request (practical limit 499), default page size 25, 1 MB payload maximum, rate limits tiered by identity domain (150-5,000 requests/min). [src2, src3]
- Watch out for: The
limitparameter caps at 500 but server can silently override it downward; always checkhasMoreattribute and implement pagination loops. [src3, src4] - Best for: Real-time individual record CRUD operations, small-batch reads (<500 records), and metadata/describe queries on Oracle Fusion Cloud ERP modules. [src5]
- Authentication: OAuth 2.0 via OCI IAM (JWT bearer for server-to-server); basic auth supported but not recommended for production. [src6]
System Profile
Oracle ERP Cloud (Fusion) is Oracle's cloud-native ERP suite covering Financials, Procurement, Project Management, and Supply Chain. The REST API surface uses the Oracle Fusion Applications REST framework, which has evolved through multiple versions (most recently Framework v3, introducing collection-based pagination for expanded child resources). This card covers the REST API specifically — SOAP services, FBDI, BI Publisher, and the Oracle Integration Cloud (OIC) adapter are separate integration surfaces with different capabilities and constraints.
| Property | Value |
|---|---|
| Vendor | Oracle |
| System | Oracle ERP Cloud (Fusion) 24B |
| API Surface | REST (HTTPS/JSON) |
| Current Framework Version | REST Framework v3+ |
| Editions Covered | All (Enterprise, Standard) |
| Deployment | Cloud |
| API Docs | Oracle Fusion Cloud REST API |
| Status | GA |
API Surfaces & Capabilities
Oracle Fusion Cloud offers multiple integration surfaces. The REST API is one of several; choosing the right surface depends on data volume, latency requirements, and integration pattern. [src5]
| API Surface | Protocol | Best For | Max Records/Request | Rate Limit | Real-time? | Bulk? |
|---|---|---|---|---|---|---|
| REST API | HTTPS/JSON | Individual record CRUD, queries <500 records | 500 (practical 499) | Tiered: 150-5,000/min | Yes | No |
| SOAP Web Services | HTTPS/XML | Legacy integrations, some metadata ops | Varies by service | Shared with REST | Yes | No |
| FBDI | CSV via UCM | Bulk data import >500 records, data migration | 100K rows/file (10 MB) | N/A (async) | No | Yes |
| BI Publisher Reports | HTTPS/XML | Bulk data export, scheduled reporting | No hard limit | N/A (async) | No | Yes |
| Business Events | Oracle AQ/JMS | Event-driven integration, CDC | N/A | N/A | Yes | N/A |
| OIC ERP Cloud Adapter | Pre-built connector | Orchestrated multi-step integrations | Depends on pattern | OIC-managed | Both | Both |
Rate Limits & Quotas
Per-Request Limits
| Limit Type | Value | Applies To | Notes |
|---|---|---|---|
| Max records per GET | 500 (practical 499) | REST API collection queries | Default 25 if limit not specified; server may override downward [src3] |
| Max request body size | 1 MB | REST API POST/PATCH/PUT | Applies to request payload [src2] |
| Max batch operations | 50 | Bulk API (batch endpoint) | Operations per single batch call [src2] |
| CSV import max rows | 100,000 | FBDI file uploads | Per file, max 10 MB file size [src2] |
| CSV export max rows | 100,000 | BI Publisher/export | Per export operation [src2] |
Rolling / Daily Limits
Rate limits in Oracle Fusion Cloud are managed at the OCI IAM identity domain level, not at the Fusion application level. [src2]
| Limit Type | Free Tier | Oracle Apps Tier | Premium Tier | Window |
|---|---|---|---|---|
| Authentication requests | 150/min | 1,000/min | 4,500/min | Per minute |
| Token management | 150/min | 1,000/min | 3,400/min | Per minute |
| Other API calls | 150/min | 1,500/min | 5,000/min | Per minute |
| Bulk API operations | 200/min | 200/min | 200/min | Per minute |
Authentication
Oracle Fusion Cloud REST APIs support multiple authentication methods, with OAuth 2.0 being the recommended approach for production integrations. [src6]
| Flow | Use When | Token Lifetime | Refresh? | Notes |
|---|---|---|---|---|
| OAuth 2.0 JWT Bearer | Server-to-server, unattended integrations | Configurable (default ~1h) | New JWT per request | Recommended for production [src6] |
| OAuth 2.0 3-Legged | User-context operations, interactive apps | Access: ~1h, Refresh: until revoked | Yes | Requires callback URL and user consent [src6] |
| Basic Auth (username/password) | Development, testing, quick prototyping | Session-based | N/A | Not recommended for production — no MFA support [src6] |
Authentication Gotchas
- OAuth requires configuring a Confidential Application in the OCI IAM domain linked to your Fusion instance — this is separate from creating an integration user in Fusion [src6]
- JWT bearer flow requires a digital certificate (self-signed works for dev; use CA-signed for production) [src6]
- Basic auth credentials break silently if admin changes the user's password or enforces MFA [src6]
- Access tokens are scoped to the integration user's Fusion security roles — the REST API enforces the same row-level security as the Fusion UI [src1]
Constraints
- 499-record ceiling: REST GET requests return at most 499 records per page. You must implement pagination loops using
offsetandhasMore. [src3, src4] - Server override on limit: Even if you set
limit=500, the server may return fewer records. Always checkhasMorerather than counting returned records. [src3] - 1 MB payload limit: POST/PATCH request bodies cannot exceed 1 MB. For larger payloads, split into multiple requests or use FBDI. [src2]
- No streaming/CDC via REST: REST API is request-response only. For event-driven patterns, use Business Events or the OIC ERP Cloud Adapter. [src5]
- FBDI mandatory for bulk: REST API is explicitly not recommended for data volumes >500 records per operation. [src5]
- Rate limits per identity domain, not per user: All integrations sharing the same OCI IAM domain share the same rate limit pool. [src2]
- No partial-success on batch: Batch operations may roll back entirely if one operation fails. [src1]
Integration Pattern Decision Tree
START - User needs to integrate with Oracle ERP Cloud (Fusion) via REST API
|-- What's the integration pattern?
| |-- Real-time (individual records, <1s)
| | |-- Data volume < 500 records/operation?
| | | |-- YES -> REST API: use standard CRUD endpoints
| | | | |-- Multi-object in single call? -> Use batch endpoint (max 50 ops)
| | | | +-- Single object -> Standard POST/PATCH/GET/DELETE
| | | +-- NO -> NOT suitable for REST API -> Use FBDI
| | +-- Need response shaping?
| | |-- Fewer fields -> Use fields parameter (reduces payload)
| | |-- Child resources -> Use expand parameter (avoids N+1)
| | +-- No links needed -> Use onlyData=true (smaller payload)
| |-- Batch/Bulk (scheduled, high volume)
| | |-- Data volume < 500 records? -> REST API acceptable
| | +-- Data volume > 500 records? -> FBDI for import, BI Publisher for export
| |-- Event-driven (webhook, CDC)
| | +-- REST API does NOT support events
| | |-- Use Business Events for ERP-originated events
| | +-- Use OIC ERP Cloud Adapter for orchestrated handling
| +-- File-based (CSV/XML) -> Use FBDI
|-- Which direction?
| |-- Inbound (writing to Fusion) -> POST/PATCH; check payload < 1 MB
| |-- Outbound (reading from Fusion) -> GET with pagination; use finder
| +-- Bidirectional -> Design conflict resolution first
+-- Error tolerance?
|-- Zero-loss -> Implement idempotency + retry with backoff on 429
+-- Best-effort -> Fire-and-forget with basic 429 retry
Quick Reference
Key Query Parameters
| Parameter | Purpose | Example | Notes |
|---|---|---|---|
limit | Max records per page | ?limit=499 | Default 25, max 500 (practical 499). Server can override. [src1] |
offset | Pagination start index | ?offset=499 | 0-indexed. offset=0 returns from first item. [src1] |
fields | Select specific fields | ?fields=InvoiceId,InvoiceNumber | Reduces payload size; comma-separated. [src1] |
expand | Include child resources | ?expand=invoiceLines | Comma-separated or expand=all. Framework v3+: returns paginated collection. [src1] |
onlyData | Omit HATEOAS links | ?onlyData=true | Default false. Cannot combine with links parameter. [src1] |
finder | Named predefined query | ?finder=PrimaryKey;InvoiceId=12345 | Uses predefined WHERE clause with bind variables. [src1] |
q | Ad-hoc filter/query | ?q=InvoiceNumber='INV-001' | SCIM-style operators: eq, ne, co, sw, gt, lt, ge, le. [src1] |
orderBy | Sort results | ?orderBy=CreationDate:desc | Ascending default. Use :asc or :desc suffix. [src1] |
totalResults | Include total count | ?totalResults=true | Performance cost — avoid for large collections. [src1] |
links | Control link relations | ?links=self,canonical | Mutually exclusive with onlyData=true. [src1] |
Common Financial REST Endpoints
| Operation | Method | Endpoint | Payload | Notes |
|---|---|---|---|---|
| List invoices | GET | /fscmRestApi/resources/v1/invoices | N/A | Paginated; use limit and offset [src7] |
| Get invoice by ID | GET | /fscmRestApi/resources/v1/invoices/{InvoiceId} | N/A | Returns single resource with child links [src7] |
| Create invoice | POST | /fscmRestApi/resources/v1/invoices | JSON | Required fields per describe endpoint [src7] |
| Update invoice | PATCH | /fscmRestApi/resources/v1/invoices/{InvoiceId} | JSON | Partial update — only changed fields [src7] |
| Delete invoice | DELETE | /fscmRestApi/resources/v1/invoices/{InvoiceId} | N/A | Not supported on all resources [src7] |
| Describe resource | GET | /fscmRestApi/resources/v1/invoices/describe | N/A | Returns metadata: fields, types, children, actions [src1] |
| Execute action | POST | /fscmRestApi/resources/v1/invoices/{id} | JSON | Custom actions defined per resource [src1] |
| Access child | GET | /fscmRestApi/resources/v1/invoices/{id}/invoiceLines | N/A | Navigate parent-child relationships [src7] |
Step-by-Step Integration Guide
1. Authenticate with OAuth 2.0 JWT Bearer
Obtain an access token from the OCI IAM domain linked to your Fusion instance. [src6]
# Request OAuth token from OCI IAM
curl -X POST "https://idcs-{tenant}.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=https://{pod}.fa.{dc}.oraclecloud.com:443urn:opc:resource:consumer::all"
Verify: Response contains access_token field with JWT string and expires_in value.
2. Query a resource collection with pagination
Fetch records using offset-based pagination. The response includes hasMore (boolean) and count (records in current page). [src1, src4]
# First page: 499 records starting from offset 0
curl -X GET "https://{pod}.fa.{dc}.oraclecloud.com/fscmRestApi/resources/v1/invoices?limit=499&offset=0&onlyData=true&fields=InvoiceId,InvoiceNumber,InvoiceAmount" \
-H "Authorization: Bearer {access_token}"
Verify: Response JSON contains items array, hasMore boolean, and count integer.
3. Handle pagination loop for large datasets
When the dataset exceeds 499 records, implement an offset-based loop. [src4]
import requests
def fetch_all_records(base_url, resource, token, fields=None):
all_items, offset, has_more = [], 0, True
headers = {"Authorization": f"Bearer {token}"}
while has_more:
params = {"limit": 499, "offset": offset, "onlyData": "true"}
if fields:
params["fields"] = ",".join(fields)
resp = requests.get(f"{base_url}/fscmRestApi/resources/v1/{resource}",
headers=headers, params=params)
resp.raise_for_status()
data = resp.json()
all_items.extend(data.get("items", []))
has_more = data.get("hasMore", False)
offset += len(data.get("items", []))
return all_items
Verify: Total record count matches expected total.
4. Use expand vs fields for response shaping
The expand parameter includes child resources inline (avoids N+1 queries). The fields parameter limits which fields are returned. [src1]
# Expand invoice lines, select specific fields, omit links
curl -X GET "https://{pod}.fa.{dc}.oraclecloud.com/fscmRestApi/resources/v1/invoices/{InvoiceId}?expand=invoiceLines&fields=InvoiceId,InvoiceNumber;invoiceLines:LineNumber,LineAmount&onlyData=true" \
-H "Authorization: Bearer {access_token}"
Verify: Response includes invoiceLines as nested collection with only specified fields.
5. Implement error handling with exponential backoff
Handle rate limiting (429) and transient failures with exponential backoff. [src2]
import time, requests
def resilient_request(method, url, headers, max_retries=5, **kwargs):
for attempt in range(max_retries):
resp = requests.request(method, url, headers=headers, **kwargs)
if resp.status_code == 429:
wait = min(int(resp.headers.get("Retry-After", 2 ** attempt)), 60)
time.sleep(wait)
continue
if resp.status_code >= 500:
time.sleep(2 ** attempt)
continue
resp.raise_for_status()
return resp
raise Exception(f"Max retries exceeded for {url}")
Verify: On 429, function waits and retries; on 5xx, uses exponential backoff.
Code Examples
Python: Fetch All Suppliers with Pagination
# Input: OAuth token, Fusion base URL
# Output: List of all supplier dicts with selected fields
import requests
BASE_URL = "https://{pod}.fa.{dc}.oraclecloud.com"
TOKEN = "{your_oauth_token}"
headers = {"Authorization": f"Bearer {TOKEN}"}
all_suppliers, offset, has_more = [], 0, True
while has_more:
resp = requests.get(f"{BASE_URL}/fscmRestApi/resources/v1/suppliers",
headers=headers,
params={"limit": 499, "offset": offset, "onlyData": "true",
"fields": "SupplierId,Supplier,SupplierNumber,Status"})
resp.raise_for_status()
data = resp.json()
all_suppliers.extend(data.get("items", []))
has_more = data.get("hasMore", False)
offset += len(data.get("items", []))
print(f"Total suppliers: {len(all_suppliers)}")
JavaScript/Node.js: Query with Finder Parameter
// Input: OAuth token, invoice number to look up
// Output: Invoice object matching the finder query
const fetch = require("node-fetch");
async function findInvoice(baseUrl, token, invoiceNumber) {
const url = new URL(`${baseUrl}/fscmRestApi/resources/v1/invoices`);
url.searchParams.set("finder", `PrimaryKey;InvoiceNumber=${invoiceNumber}`);
url.searchParams.set("onlyData", "true");
const resp = await fetch(url.toString(), {
headers: { Authorization: `Bearer ${token}` }
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const data = await resp.json();
return data.items?.[0] || null;
}
cURL: Quick API Tests
# Test authentication
curl -s "https://{pod}.fa.{dc}.oraclecloud.com/fscmRestApi/resources/v1/invoices?limit=5&onlyData=true" \
-H "Authorization: Bearer {token}" | python -m json.tool
# Describe resource schema
curl -s "https://{pod}.fa.{dc}.oraclecloud.com/fscmRestApi/resources/v1/invoices/describe" \
-H "Authorization: Bearer {token}" | python -m json.tool
# Filter with q parameter
curl -s "https://{pod}.fa.{dc}.oraclecloud.com/fscmRestApi/resources/v1/invoices?q=CreationDate%20gt%20%272026-01-01%27&limit=10&onlyData=true" \
-H "Authorization: Bearer {token}" | python -m json.tool
Data Mapping
REST API Response Structure
| Response Field | Type | Description | Present When |
|---|---|---|---|
items | Array | Array of resource objects | Collection GET |
count | Integer | Number of records in current page | Collection GET |
hasMore | Boolean | Whether more pages exist | Collection GET |
totalResults | Integer | Total matching records | Only with totalResults=true |
links | Array | HATEOAS navigation links | When onlyData is false (default) |
Data Type Gotchas
- Oracle Fusion datetime fields include timezone offset (e.g.,
2026-03-01T10:30:00+00:00). Always parse with timezone awareness. [src1] - Amount fields are numbers, not strings. Multi-currency environments require fetching the currency code from a separate field. [src7]
- Boolean fields are often stored as
Y/Nstrings (not JSON true/false). Check thedescribeendpoint. [src1] - Flexfield values (DFF/EFF) are accessed via child resources, not direct parent fields. [src1]
- The
qfilter uses SCIM operators (eq, co, sw, gt, lt), not SQL. String values must be in single quotes. [src1]
Error Handling & Failure Points
Common Error Codes
| Code | Meaning | Cause | Resolution |
|---|---|---|---|
| 400 | Bad Request | Malformed JSON, invalid field names | Check payload against describe endpoint [src1] |
| 401 | Unauthorized | Expired/invalid token | Refresh OAuth token; verify integration user status [src6] |
| 403 | Forbidden | Insufficient security roles | Grant required Fusion security roles [src6] |
| 404 | Not Found | Wrong resource path or nonexistent record | Verify endpoint URL and resource version [src1] |
| 429 | Too Many Requests | Rate limit exceeded | Exponential backoff; check Retry-After header [src2] |
| 500 | Internal Server Error | Server-side processing failure | Retry with backoff; check Fusion EM Console logs [src1] |
| 503 | Service Unavailable | Maintenance window | Check Oracle Cloud Status Dashboard [src2] |
Failure Points in Production
- Silent pagination truncation: Server can return fewer records than
limitwithout explanation. Fix:Always use hasMore boolean instead of comparing count to limit. [src3] - OAuth token expiry in long-running jobs: Pagination loops may exceed token lifetime (~1h). Fix:
Track token expiry and refresh proactively before expiration. [src6] - Flexfield access requires separate requests: DFF/EFF not included in parent resource by default. Fix:
Use expand parameter for DFF/EFF child resources. [src1] - Finder parameter bind variable format: Requires semicolons (not & or =). API returns 200 with empty results on wrong format. Fix:
Use semicolons; verify finders via describe endpoint. [src1] - Batch endpoint rollback behavior: May roll back all operations if one fails. Fix:
Test per resource in non-prod; implement single-record fallback. [src1]
Anti-Patterns
Wrong: Fetching all records without pagination
# BAD - Assumes single request returns all records
resp = requests.get(f"{base_url}/invoices", headers=headers)
all_invoices = resp.json()["items"] # Only gets first 25 records (default limit)
Correct: Always implement pagination with hasMore check
# GOOD - Paginates through all records using hasMore
all_invoices, offset, has_more = [], 0, True
while has_more:
resp = requests.get(f"{base_url}/invoices", headers=headers,
params={"limit": 499, "offset": offset, "onlyData": "true"})
data = resp.json()
all_invoices.extend(data["items"])
has_more = data.get("hasMore", False)
offset += len(data["items"])
Wrong: Using REST API for bulk data import
# BAD - Creating 10,000 records one by one via REST API
for invoice in ten_thousand_invoices:
requests.post(f"{base_url}/invoices", json=invoice, headers=headers)
# Takes hours, risks rate limiting, no batch rollback
Correct: Use FBDI for bulk operations
# GOOD - For >500 records, use File-Based Data Import
# 1. Generate CSV in FBDI template format
# 2. Upload CSV to UCM via REST
# 3. Invoke ESS import job via REST
# 4. Poll ESS job status until complete
resp = requests.get(f"{base_url}/erpintegrations/v1/ess-job-status/{job_id}",
headers=headers)
Wrong: Fetching full payloads when only a few fields needed
# BAD - Returns all fields including HATEOAS links
curl "https://{pod}.fa.{dc}.oraclecloud.com/fscmRestApi/resources/v1/invoices?limit=499"
Correct: Use fields + onlyData to minimize payload
# GOOD - Only returns needed fields, no links
curl "https://{pod}.fa.{dc}.oraclecloud.com/fscmRestApi/resources/v1/invoices?limit=499&onlyData=true&fields=InvoiceId,InvoiceNumber,InvoiceAmount"
Common Pitfalls
- Default limit is 25, not 500: Forgetting the
limitparameter returns only 25 records per page. Fix:Always explicitly set limit=499 in production code. [src1, src3] - expand behavior changed in Framework v3: Prior to v3,
expandreturned flat arrays. In v3+, it returns paginated collections. Fix:Handle expanded children as collections with hasMore/count/offset. [src1] - q parameter syntax vs SQL: The
qfilter uses SCIM operators, not SQL WHERE clauses. Using SQL syntax silently returns zero results. Fix:Use SCIM operators: eq, ne, co, sw, gt, lt, ge, le. [src1] - totalResults has performance cost: Adding
totalResults=trueforces counting all records, increasing response time up to 10x. Fix:Only use on first request if needed; omit for subsequent pages. [src1] - Describe endpoint versioning: Metadata may differ between resource versions (e.g.,
11.13.18.05vsv1). Fix:Pin to a specific version and use it consistently. [src1] - Concurrent requests share rate limit pool: Multiple integration apps on the same OCI IAM domain compete for the same quota. Fix:
Monitor API usage across all apps; consider separate IAM domains. [src2]
Diagnostic Commands
# Test authentication — simple GET to verify token
curl -s -o /dev/null -w "%{http_code}" \
"https://{pod}.fa.{dc}.oraclecloud.com/fscmRestApi/resources/v1/invoices?limit=1" \
-H "Authorization: Bearer {token}"
# Expected: 200 (success) or 401 (token issue)
# Discover available resources — list resource catalog
curl -s "https://{pod}.fa.{dc}.oraclecloud.com/fscmRestApi/resources/" \
-H "Authorization: Bearer {token}" | python -m json.tool
# Describe resource schema
curl -s "https://{pod}.fa.{dc}.oraclecloud.com/fscmRestApi/resources/v1/invoices/describe" \
-H "Authorization: Bearer {token}" | python -m json.tool
# Test q filter syntax
curl -s "https://{pod}.fa.{dc}.oraclecloud.com/fscmRestApi/resources/v1/invoices?q=InvoiceNumber%20eq%20%27INV-001%27&onlyData=true&limit=1" \
-H "Authorization: Bearer {token}" | python -m json.tool
Version History & Compatibility
| Framework Version | Approx. Release | Status | Breaking Changes | Migration Notes |
|---|---|---|---|---|
| REST Framework v3+ | 2022 | Current | expand returns collection resources (paginated) | Update expand parsing to handle collection structure |
| REST Framework v2 | 2019 | Legacy | N/A | Flat expand responses; no child pagination |
| REST Framework v1 | 2016 | Deprecated | N/A | Limited query parameters; basic CRUD only |
Resource versions (e.g., 11.13.18.05 vs v1) are independent of framework versions. Oracle does not publish a fixed deprecation timeline — monitor Release Readiness documents for each quarterly update. [src1]
When to Use / When Not to Use
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Real-time individual record CRUD (<500 records) | Bulk data import/export (>500 records) | FBDI for import, BI Publisher for export [src5] |
| Querying with complex filters (q parameter) | Full data migration or initial load | FBDI + ESS scheduled processes [src5] |
| Metadata discovery (describe endpoint) | Event-driven / CDC integration | Business Events + OIC Adapter [src5] |
| Small-batch reads with field selection | Streaming or real-time notifications | Business Events subscriptions [src5] |
| Prototyping and testing with cURL/Postman | Scheduled nightly sync of entire tables | BI Publisher reports + OIC [src5] |
Important Caveats
- Rate limits are subject to change with each Oracle Cloud release and may vary by region and pod. Always verify current limits against your specific identity domain's tier. [src2]
- The 499-record practical limit is widely observed behavior, but Oracle's official documentation states 500 as the maximum. Test with your specific instance. [src3]
- REST API endpoint availability varies by Oracle Fusion Cloud module. Not all ERP objects have REST endpoints. [src7]
- Oracle frequently adds new REST resources in quarterly updates. The resource catalog on your instance is the authoritative source. [src1]
- Performance depends on pod, security rule complexity, and concurrent tenant usage. Benchmark with production-like data volumes before go-live.