Oracle HCM Atom Feeds: REST API Change Detection Done Right
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:
| Collection | Triggers on |
|---|---|
newhire | An employee is hired, a terminated employee is rehired, or a future-dated new hire record is created |
empassignment | Assignment created/updated/corrected/deleted, salary changes, manager changes, assignment DFF/EFF changes |
empupdate | Name, email, phone, address, passport, driver’s license, citizenship, national identifier, or person DFF/EFF changes |
payupdate | Payroll assigned or updated for an employee |
termination | An employee is terminated |
cancelworkrelship | A work relationship is cancelled (no-show, rejected offer, reverted transfer) |
workrelshipupdate | The 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": "[email protected]",
"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:
- Filter/sort on
updatedif you want to act on a transaction once it’s actually effective (the common choice for payroll or benefits systems that shouldn’t apply a change before its effective date). - Filter/sort on
publishedif you want to act the moment the transaction is entered, regardless of its effective date (useful for downstream systems that need advance notice, like provisioning a future hire’s accounts before day one).
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
- Polling REST resources for changes instead of subscribing to a feed. This is the exact anti-pattern Oracle’s docs warn against — it’s unreliable for detecting deletions and cancellations, and wastes calls against your rate limit.
- Not persisting the checkpoint. If
lastProcessedEntrylives only in memory, every restart replays the entire feed history from the beginning (or from whateverupdated-minyou hardcoded). - Subscribing to the wrong collection for the event you care about. A name change is an
empupdateevent, not anempassignmentevent — check the Employee Feeds table for the exact trigger list before you build. - Ignoring the
updatedvspublisheddistinction on future-dated transactions. See above — this decides whether your integration reacts early or on the effective date. - Treating the feed as the full record. The entry’s
Contextblock has the attributes relevant to that event, not the whole resource — follow the entry’s link to/workers/<ID>when you need the complete current state.
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.