← Back to blog

Oracle HCM Atom Feeds: REST API Change Detection Done Right

By Mostafa Mansour 7 min read Oracle HCMREST APIAtom FeedsIntegrationChange Detection

Oracle’s own REST API docs say it plainly, and every experienced Fusion integrator has hit the reason why: you must not use REST APIs for detecting data changes in HCM Cloud. There’s no modifiedSince filter on /workers that reliably catches every update, and polling a large collection on a timer either misses corrections and deletions or burns through your rate limit for no reason. Atom feeds exist specifically to solve this: they’re event-driven, near-real-time, and — unlike a REST poll — they tell you what changed, not just that the record’s current state differs from what you last saw. Almost no practical write-up covers how to actually consume one; this post does, with real feed names and a working polling pattern straight from Oracle’s own reference implementation.

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

Why not just poll /workers on a timer

A scheduled GET /workers?q=LastUpdateDate>=... looks reasonable until you hit its actual failure modes: it can’t see a work relationship that was cancelled (the row still “exists,” just changed state, or in some cases the object your query was watching isn’t the object where the change landed), it re-downloads full records on every poll even when nothing changed, and it has no concept of “this employee was hired, then immediately corrected” as two distinct events — you just see the final state. Atom feeds are triggered by the actual transaction (hire, correction, termination, assignment change) and hand you one entry per event, so you can process history instead of reconstructing it from snapshots.

The feed URL pattern

Atom feeds are organized into workspaces and collections. For the employee workspace:

https://acme.fa.us2.oraclecloud.com/hcmRestApi/atomservlet/employee/<collection>

The collections that matter most for a typical HR integration:

CollectionTriggers on
newhireAn employee is hired, a terminated employee is rehired, or a future-dated new hire record is created
empassignmentAssignment created/updated/corrected/deleted, salary changes, manager changes, assignment DFF/EFF changes
empupdateName, email, phone, address, passport, driver’s license, citizenship, national identifier, or person DFF/EFF changes
payupdatePayroll assigned or updated for an employee
terminationAn employee is terminated
cancelworkrelshipA work relationship is cancelled (no-show, rejected offer, reverted transfer)
workrelshipupdateThe work relationship of an active employee is updated (hire date correction, rehire flags, and similar)

So a system that needs to know about new hires and terminations subscribes to two feeds — newhire and termination — not one big “everything changed” firehose.

Reading a feed

To see the newest entries, just hit the collection URL:

curl -u integration.user \
  "https://acme.fa.us2.oraclecloud.com/hcmRestApi/atomservlet/employee/termination"

To pick up only what’s changed since your last check, filter on updated-min (or published-min if you’re processing by publish date rather than effective date — the two matter differently for future-dated HR transactions) using an ISO 8601 timestamp:

curl -u integration.user \
  "https://acme.fa.us2.oraclecloud.com/hcmRestApi/atomservlet/employee/newhire?updated-min=2026-07-21T00:00:00.000Z"

You can also bound a range with updated-min + updated-max (lower bound exclusive, upper bound inclusive), and pull the archived version of a feed with archive=yes once entries have aged out of the live feed.

What an entry actually contains

Each entry is an Atom entry with an HCM-specific content block. A newhire entry looks like this (shape confirmed against Oracle’s reference, values anonymized):

{
  "Context": [
    {
      "PrimaryPhoneNumber": "+1-555-0100",
      "PersonId": "300000012345678",
      "PersonName": "Jordan Alvarez",
      "PeriodOfServiceId": "300000012345679",
      "EffectiveStartDate": "2026-07-20",
      "EffectiveDate": "2026-07-20",
      "WorkerType": "EMP",
      "PeriodType": "First Employment",
      "PersonNumber": "100002",
      "WorkEmail": "jordan.alvarez@example.com",
      "DMLOperation": "INSERT"
    }
  ]
}

The entry also carries a link back to the REST resource the event happened on — /workers/<ID> with reltype of workers — so once you’ve read the event, you can follow that link to pull the full current record via the regular REST API if you need more than the feed’s own attributes. That’s the pattern: the feed tells you that something happened and gives you the key identifiers; you use the workers endpoint (or whichever resource the feed points at) for the full picture.

Polling reliably: the pattern Oracle documents

This is the part almost nobody writes up with actual code. The reliable polling loop keeps a checkpoint of the last processed entry’s timestamp, walks every page of new entries in order, and only advances the checkpoint after each entry is successfully processed — so a crash mid-batch resumes from the last confirmed entry, not the last page fetched:

pageSize = 10
page = 1
updatedMin = lastProcessedEntry   // beginning of time on first run

repeat {
    entries = GET /atomservlet/employee/empupdate
                  ?updated-min=${updatedMin}
                  &page-size=${pageSize}
                  &page=${page}
                  &orderBy=updated:asc

    foreach entry in entries {
        process(entry)
        if (processed successfully) {
            lastProcessedEntry = entry.updatedDate
        } else {
            break repeat
        }
    }
    page++
} while (entries.size == pageSize)

Sort ascending on updated (or published, if you’ve decided to process future-dated transactions as soon as they’re recorded rather than when they take effect — see the note below) so you never skip an entry that landed between two polling runs. Persist lastProcessedEntry somewhere durable between runs, not in memory, or a process restart re-reads everything from the beginning.

updated vs published: the future-dated transaction gotcha

HCM transactions are frequently future-dated — a promotion entered today with an effective date three weeks out. Oracle’s feeds expose both an updated timestamp (when the record was last touched) and a published timestamp, and which one you filter and sort on determines when your integration reacts:

Picking the wrong one silently changes your integration’s timing — this is worth deciding deliberately, not defaulting to whichever example you copy first.

Common gotchas

Where this fits with everything else

Atom feeds solve one specific problem — reliable change notification — and hand off to the regular REST API for everything else: fetching the full record (workers reference), filtering with q, or reasoning about effective-dated data once you’ve pulled it (see our effective dates guide for RangeMode/RangeStartDate on the resource itself). For the rest of the HCM REST surface, see the Oracle HCM API guide and the endpoint catalog.


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