← Back to blog

Oracle Fusion REST API Rate Limits: Handling 429 Too Many Requests

By Mostafa Mansour 7 min read Oracle FusionREST APIRate Limiting429Integration

A nightly sync job that’s worked fine for months starts throwing 429 Too Many Requests partway through, and the failure looks random — same code, same records, no error last week. It isn’t random. Oracle Fusion REST APIs are rate-limited at the identity-domain level, and once an integration’s call volume crosses that line, every request gets throttled until the window resets. The Customer Connect forums have multiple open threads asking what the actual limit is and how to handle the response, and the answer is scattered across a few generic pages — nothing walks through the mechanism and the recovery pattern together. This is that guide.

All examples use an anonymized pod (acme.fa.us2.oraclecloud.com) and placeholder identifiers — swap in your own.

Why 429 happens

Rate limiting on Oracle Fusion REST APIs is enforced at the identity domain (IDCS/IAM) level, not by the individual Fusion module you’re calling. That means a limit isn’t really “the workers API’s limit” or “the invoices API’s limit” — it’s a ceiling on how much total API traffic your identity domain type allows in a given window, shared across every integration authenticating against it.

Two things follow from that:

Reading the response

A throttled request comes back like this:

HTTP/1.1 429 Too Many Requests
Retry-After: 30

Retry-After (seconds) is the signal to respect — it’s Oracle telling you how long to wait before the next attempt has a real chance of succeeding. Not every response includes it consistently, so treat it as authoritative when present and fall back to your own backoff schedule when it’s absent.

The pattern that doesn’t work

# Don't do this — retrying immediately just re-triggers the same throttle
while true; do
  curl -s -o /dev/null -w "%{http_code}" \
    -u integration.user \
    "https://acme.fa.us2.oraclecloud.com/hcmRestApi/resources/11.13.18.05/workers?limit=500&offset=$OFFSET"
done

A tight retry loop with no delay doesn’t recover from a 429 — it extends the throttle window, because every immediate retry counts against the same limit that triggered the 429 in the first place.

Exponential backoff with jitter

The recovery pattern: on a 429, wait, then retry with a growing delay, capped at a maximum, with a small random jitter added so that if multiple workers hit the limit at the same moment, they don’t all retry in lockstep and re-trigger each other.

attempt=0
max_attempts=5
base_delay=2     # seconds
max_delay=60     # seconds

while [ $attempt -lt $max_attempts ]; do
  status=$(curl -s -o response.json -w "%{http_code}" \
    -u integration.user \
    "https://acme.fa.us2.oraclecloud.com/hcmRestApi/resources/11.13.18.05/workers?limit=500&offset=$OFFSET&orderBy=PersonId")

  if [ "$status" = "429" ]; then
    retry_after=$(curl -s -I -u integration.user \
      "https://acme.fa.us2.oraclecloud.com/hcmRestApi/resources/11.13.18.05/workers?limit=1" \
      | grep -i '^Retry-After:' | awk '{print $2}' | tr -d '\r')

    delay=${retry_after:-$(( base_delay * (2 ** attempt) ))}
    delay=$(( delay > max_delay ? max_delay : delay ))
    jitter=$(( RANDOM % 3 ))
    sleep $(( delay + jitter ))

    attempt=$(( attempt + 1 ))
    continue
  fi

  break
done

Same idea as pseudo-code, module-agnostic:

attempt = 0
while attempt < max_attempts:
    response = call_api(request)
    if response.status == 429:
        delay = response.headers.get("Retry-After") or min(base_delay * (2 ** attempt), max_delay)
        sleep(delay + random_jitter())
        attempt += 1
        continue
    break

Cap the attempts. A 429 that keeps recurring after several backed-off retries usually means the integration’s total call volume genuinely exceeds what the identity domain allows — more patience won’t fix that, a design change will (see below). Five attempts with a 60-second cap is a reasonable ceiling; looping indefinitely just hides a capacity problem behind a job that never finishes.

When backoff isn’t the real fix

Backoff handles occasional throttling. It doesn’t fix an integration whose steady-state call volume is structurally too high. If a job is 429-ing repeatedly rather than occasionally, the actual fix is usually one of:

Transactional vs. bulk: know which one you’re building

TransactionalBulk
PatternSingle-record create/update, on-demand queryFull extracts, mass loads, scheduled syncs
VolumeLow, spread over timeHigh, concentrated in a run
Right toolREST APIFBDI / HCM Data Loader / erpintegrations
429 riskLow, occasional — backoff is sufficientHigh if forced through REST — redesign, don’t just retry

A REST integration that started as “sync a handful of records when they change” and grew into “extract the whole table nightly” is the most common way a previously-reliable job starts 429-ing — the call pattern outgrew the tool it was built on.

Common gotchas

Where this fits with everything else

Rate limiting is a write-and-read-path concern that sits alongside the other resilience mechanics we’ve covered: it’s independent of ETag/If-Match concurrency (a 429 means you never got far enough to conflict with anyone), and it compounds with pagination — a large paged extraction is exactly the shape of workload most likely to trip a rate limit, which is why the two are worth reading together. For the rest of the request lifecycle — auth, q filters, finders — see the full Oracle Fusion API guide and the endpoint catalog.


This post is part of our complete Oracle Fusion API guide — auth, base URLs, q filters, finders, and key endpoints in one place.