know.2nth.ai Business biz erp oracle-scm
biz/erp · Oracle SCM · Skill Leaf

Oracle Fusion
Cloud SCM.

The closed-source, multi-tenant SaaS suite covering nine pillars of supply chain and manufacturing. Every pillar shares one REST surface at /fscmRestApi/resources/11.13.18.05/. Expensive, complex, and the system of record for enterprises that have committed to Oracle. This page covers the integration surface, not the Fusion UI.

Production REST 9 pillars Closed-source SaaS v26B

Nine pillars, one REST surface, no open-source equivalent.

Oracle Fusion Cloud SCM is Oracle's cloud-native supply chain and manufacturing suite. It replaced E-Business Suite (EBS) for new deployments and is the canonical choice for enterprises standardised on Oracle Cloud Applications. There is no drop-in open-source equivalent for the full suite — if a customer is on Oracle, the REST APIs documented here are the integration surface.

Base URL pattern:

https://<pod>.fa.<region>.oraclecloud.com/fscmRestApi/resources/11.13.18.05/<resource>

# <pod>   — customer-specific, e.g. eabc-dev1, eabc-prod
# <region> — Oracle Cloud region, e.g. em2 (Frankfurt), ap1 (Sydney)
# SA customers: hosted em2 or ap1 — no Johannesburg Fusion Apps region
# The /11.13.18.05/ segment is the resource version, not the app version

Every resource supports GET (list + single), POST (create), PATCH (partial update), DELETE, and in many cases BATCH. The nine pillars share this surface — planning, inventory, manufacturing, maintenance, order management, logistics, PLM, procurement, and analytics.

Fusion is SOR. 2nth is middleware.

The canonical pattern: read from Fusion, run AI-assisted workflows (demand planning, reorder suggestions, quality alerts), write approved decisions back. Never replicate Fusion's data model — index against it. Cache on your side, refresh via Business Events or scheduled polls.

Basic auth for trusted networks. OAuth2 for everything else.

Two mechanisms. Basic auth with a dedicated integration user is simplest for server-to-server in a private network. OAuth2 via IDCS is required for anything public-facing or multi-tenant.

# Basic auth — integration user with Integration Specialist role
# plus module-specific duty roles (Item Inquiry, PO Entry, etc.)

import os, requests
from requests.auth import HTTPBasicAuth

POD = "https://eabc-dev1.fa.em2.oraclecloud.com"
BASE = f"{POD}/fscmRestApi/resources/11.13.18.05"

resp = requests.get(
    f"{BASE}/items",
    params={"limit": 25,
            "fields": "ItemId,ItemNumber,Description,ItemStatusValue"},
    auth=HTTPBasicAuth(os.environ["FA_USER"], os.environ["FA_PASS"]),
    headers={"REST-Framework-Version": "4"},
    timeout=30,
)
resp.raise_for_status()
for it in resp.json()["items"]:
    print(it["ItemNumber"], "-", it["Description"])
# OAuth2 — client credentials grant via IDCS / OCI IAM

IDCS_URL = "https://idcs-xxxx.identity.oraclecloud.com"
CLIENT_ID = os.environ["IDCS_CLIENT_ID"]
CLIENT_SECRET = os.environ["IDCS_CLIENT_SECRET"]

token_resp = requests.post(
    f"{IDCS_URL}/oauth2/v1/token",
    data={
        "grant_type": "client_credentials",
        "scope": f"{POD}/urn:opc:resource:fa:instanceid=xxxx",
    },
    auth=HTTPBasicAuth(CLIENT_ID, CLIENT_SECRET),
)
access_token = token_resp.json()["access_token"]
# Tokens valid ~1 hour. Cache and refresh before expiry.

# Use the token
resp = requests.get(
    f"{BASE}/items",
    params={"limit": 25, "fields": "ItemId,ItemNumber"},
    headers={"Authorization": f"Bearer {access_token}",
             "REST-Framework-Version": "4"},
)

Never put Fusion creds in a browser.

Always proxy through your backend. Never use a privileged user like fa_admin for integrations. Never commit credentials to Git — use OCI Vault, AWS Secrets Manager, or equivalent. Rotate passwords quarterly.

Query params, pagination, ETags, and BATCH.

Fusion's REST surface is consistent across all nine pillars. Learn these patterns once and they apply everywhere. The gotchas are in the defaults — verbose responses, unpinned framework versions, and absent pagination.

Param Purpose Example
limitPage size (max 500)limit=100
offsetPagination offsetoffset=200
fieldsRestrict response fieldsfields=ItemId,ItemNumber
onlyDataStrip links arraysonlyData=true
expandInline child collectionsexpand=ItemRevision
qFilter expression (SQL-like)q=ItemStatusValue='Active'
finderNamed queryfinder=findByItemNumber;ItemNumber=X
orderBySortorderBy=CreationDate:desc

Pagination loop. Always set limit and loop on hasMore. Never request totalResults=true unless you need the count — it forces a second aggregate query.

def fetch_all(base, path, params=None, auth=None):
    params = dict(params or {})
    params.setdefault("limit", 500)
    offset = 0
    while True:
        params["offset"] = offset
        r = requests.get(
            f"{base}/{path}", params=params, auth=auth,
            headers={"REST-Framework-Version": "4"}, timeout=60,
        )
        r.raise_for_status()
        body = r.json()
        yield from body["items"]
        if not body.get("hasMore"):
            break
        offset += params["limit"]

# Usage
for item in fetch_all(BASE, "items",
        {"fields": "ItemNumber,Description",
         "q": "ItemStatusValue='Active'"}, auth=AUTH):
    process(item)

ETags for optimistic concurrency. Capture the ETag from GET, pass it as If-Match on PATCH. A 412 Precondition Failed means someone else edited the record — re-GET, re-apply, re-PATCH.

# GET with ETag captured
r = requests.get(url, auth=auth, headers={"REST-Framework-Version": "4"})
etag = r.headers["ETag"]

# PATCH with If-Match
resp = requests.patch(
    url, json=changes, auth=auth,
    headers={"REST-Framework-Version": "4",
             "If-Match": etag,
             "Content-Type": "application/json"},
)
if resp.status_code == 412:
    # re-GET and retry with fresh ETag
    ...

Creating a sales order. POST to salesOrdersForOrderHub with source system keys for idempotency. Fusion deduplicates on (SourceTransactionSystem, SourceTransactionNumber).

order = {
    "SourceTransactionNumber": "ECOM-2026-000042",
    "SourceTransactionSystem": "SHOPIFY_ZA",
    "TransactionalCurrencyCode": "ZAR",
    "BusinessUnitName": "Acme ZA Business Unit",
    "BuyingPartyNumber": "CUST-00042",
    "RequestedShipDate": "2026-04-20T08:00:00+02:00",
    "lines": [{
        "SourceTransactionLineId": "1",
        "ProductNumber": "WIDGET-ZA-001",
        "OrderedQuantity": 2,
        "OrderedUOMCode": "Ea",
        "UnitSellingPrice": 299.00
    }]
}

resp = requests.post(
    f"{BASE}/salesOrdersForOrderHub",
    json=order, auth=AUTH,
    headers={"REST-Framework-Version": "4",
             "Content-Type": "application/json"},
)
# Capture X-ORACLE-DMS-ECID from response for SR troubleshooting
ecid = resp.headers.get("X-ORACLE-DMS-ECID")

What each pillar covers and its key resources.

Every pillar exposes resources under the same /fscmRestApi/ base. This is the map. The resources listed are the ones you will actually use — not the full catalog, which runs to hundreds.

Pillar Key resources Typical integration
Supply Chain Planning planningCycles, demandForecasts, salesAndOperationsPlans Feed POS/e-com demand signals; read replenishment plans
Inventory Management items, itemsV2, inventoryOnhandBalances, inventoryStagedTransactions Master data sync, stock visibility, receipt/issue posting
Manufacturing workOrders, workDefinitions, standardOperations MES dispatch, shop floor IoT, WIP tracking
Maintenance maintenanceWorkOrders, assets, maintenancePrograms Asset telemetry to preventive maintenance triggers
Order Management salesOrdersForOrderHub, salesOrdersForCreation, holds E-com/retail order capture, hold release automation
Logistics (OTM/OWMS/GTM) trips, shipments, receivingReceiptRequests, tradeTransactions Carrier booking, 3PL sync, customs declarations
Product Lifecycle (PLM) itemsV2 (PLM fields), changeOrders, newItemRequests PIM sync, engineering change orchestration
Procurement purchaseOrders, suppliers, requisitions, negotiations Supplier onboarding, PO automation, 3-way matching
Analytics (FDI) Prebuilt subject areas on ADW; OTBI; BI Publisher Downstream BI, nightly extracts to data lake

What SA-specific means for Oracle Fusion.

Fusion has no SA-specific localisation (unlike Brazil with NFe/SPED). Getting it right is a configuration and integration-extension exercise. These are the things that matter.

ZAR currency

Standard precision: 2 decimal places. Extended precision: 4dp for internal unit prices. Always round to 2dp on external-facing fields — Fusion rejects 4dp on some price attributes.

VAT at 15%

Tax regime ZA_VAT, standard rate 15%. SA retail integrations run VAT-inclusive pricing with reverse-calculation. B2B runs VAT-exclusive with tax added at invoice. Confirm which mode your tenant uses before building anything that touches prices.

SARS customs (SAD 500)

Imports require SAD 500 filed with SARS Customs. GTM generates trade transaction data; EDI provider (EasyClear, CIS, Core Freight) converts to SARS format. HS 2022 tariff codes on the item master, updated annually from ITAC.

B-BBEE classification

Stored on the BusinessClassifications child collection of the Supplier resource. Levels 1-8, EME, QSE, Black-Owned, Black Women-Owned. Certificates expire annually — build monitoring for 30/14/7 day warnings.

POPIA compliance

Fusion holds PII (names, addresses, ID numbers). Document it in your processing register. Cross-border transfer (pods in Frankfurt or Sydney) needs a DPA with Oracle. Implement anonymisation for deletion requests — PATCH PII to pseudonyms, keep transaction history for SARS (5-year retention).

Load-shedding resilience

SA grid is unreliable. Never call Fusion directly from request handlers. Use the outbox pattern: write intent locally, worker drains to Fusion with retries. Circuit breakers on all Fusion calls. Persistent retry queues (Redis Streams, Postgres, SQS).

B-BBEE supplier query. Pull suppliers with their classification data for procurement scorecard reporting.

# Query suppliers with B-BBEE classifications
resp = requests.get(
    f"{BASE}/suppliers",
    params={
        "fields": "Supplier,TaxpayerId,SupplierTypeCode",
        "expand": "BusinessClassifications",
        "q": "TaxpayerCountry='ZA'",
        "onlyData": "true",
        "limit": 500,
    },
    auth=AUTH,
    headers={"REST-Framework-Version": "4"},
)
for s in resp.json()["items"]:
    classifications = [c["Classification"]
                       for c in s.get("BusinessClassifications", [])]
    print(s["Supplier"], "|", ", ".join(classifications))

Production failure modes.

Six specific ways Oracle Fusion will burn you if you skip the fine print. Every one of these has cost someone a production incident.

REST-Framework-Version not set

If you omit this header, Fusion defaults to v1 — verbose, inconsistent, and liable to change shape when Oracle ships a quarterly update. Pin to 4. Review release readiness notes before bumping. Your integration will silently break otherwise.

Pagination not implemented

Default page size is small and undocumented. Always set limit (max 500) and loop on hasMore. Never request totalResults=true unless you need the count — it forces an expensive second query on every call.

ETag concurrency races

PATCH without If-Match can silently clobber concurrent edits. Always capture ETags from GET and pass them on write. Handle 412 Precondition Failed with re-read and retry.

Verbose responses (20 MB for 1000 items)

Default responses include 50+ attributes per record. Always use fields= to restrict. Add onlyData=true to strip the links arrays that bloat every response. Payload drops 5-20x.

Rate limits are unpublished

Oracle does not publish universal limits, but sustained >20 rps from a single user triggers 429s. Bursts of 100+ rps get 503d. Build exponential backoff with jitter. Prefer BATCH for bulk work — it counts as one request.

Date-effective resource duplicates

Items, supplier sites, and customer accounts are date-effective. Forgetting the Effective-Of: RangeMode=CURRENT header returns all versions including expired ones. Confusing duplicates that look like bugs but are just history.

Use when / skip when.

Use Oracle Fusion SCM when Skip it when
The customer has standardised on Oracle Cloud Applications The customer is on EBS or JD Edwards (different products, different APIs)
You need a full suite — planning, manufacturing, procurement, logistics, PLM — under one roof You only need inventory + orders and the budget is tight (ERPNext or a lightweight stack is cheaper)
Multi-site, multi-currency, complex supply chains across geographies Single-site, single-currency, simple product catalog (Oracle is wildly overspec)
Regulatory requirements demand an enterprise-grade audit trail You need fast iteration and full control over the codebase (Fusion is closed-source SaaS)
Fusion Data Intelligence provides out-of-the-box analytics you would otherwise build from scratch You already have a mature data warehouse and BI stack — FDI adds cost without enough incremental value

The honest take.

Oracle Fusion is expensive, complex, and slow to configure. Implementation timelines measured in months, not weeks. Licensing is opaque. But for organisations that have committed to Oracle, the REST surface is solid, the data model is well-thought-out, and the nine-pillar coverage means fewer point-to-point integrations. The cost is real; the coverage is also real.

How oracle-scm connects in the tree.

Oracle Fusion is the heavyweight in the ERP sub-domain. It connects to infrastructure for the proxy layer, to open-source alternatives for customers who cannot justify the licensing, and to analytics for the data that lives downstream.

Go deeper.

Oracle's docs are extensive. The canonical SKILL.md in the 2nth-ai/skills repo is the integration-focused distillation. Always check the current release (26B) — Fusion ships quarterly and fields do change.