Oracle Cloud Jobs API.
Pull enterprise and Fortune 500 job requisitions straight from Oracle's HCM Recruiting Cloud REST API — paginated JSON listings plus a details endpoint for full descriptions, skills, and workplace metadata.
Try the API.
Test Jobs and Feed endpoints against https://connect.jobo.world with live request/response examples, then copy ready-to-use curl commands.
What's in every response.
Data fields, real-world applications, and the companies already running on Oracle Cloud.
- Full HTML Job Descriptions
- Qualifications & Responsibilities
- Structured Skills List
- Department & Business Unit
- Workplace Type & Schedule
- Custom Flex Fields
- 01Enterprise Job Aggregation
- 02Fortune 500 Hiring Tracking
- 03Global Talent Market Monitoring
- 04Large-Scale Requisition Extraction
How to scrape Oracle Cloud.
Step-by-step guide to extracting jobs from Oracle Cloud-powered career pages—endpoints, authentication, and working code.
import re
import requests
from urllib.parse import urlparse
SITE_NUMBER_PATTERNS = [
re.compile(r"""siteNumber\s*[:=]\s*['"]([^'"]+)['"]""", re.IGNORECASE),
re.compile(r'"siteNumber"\s*:\s*"([^"]+)"', re.IGNORECASE),
re.compile(r"siteNumber=(CX_[^&\"'\s]+)", re.IGNORECASE),
]
def resolve_site_number(careers_url: str) -> str:
"""Resolve the Oracle Cloud siteNumber for a careers URL."""
# A /sites/<segment> that starts with "CX" IS the siteNumber.
path = urlparse(careers_url).path
match = re.search(r"/sites/([^/?#]+)", path, re.IGNORECASE)
if match and match.group(1).upper().startswith("CX"):
return match.group(1)
# Vanity slugs embed the real siteNumber in the careers-page HTML.
html = requests.get(careers_url, timeout=30).text
for pattern in SITE_NUMBER_PATTERNS:
found = pattern.search(html)
if found:
return found.group(1)
# Most production Oracle tenants default to CX_1.
return "CX_1"
careers_url = "https://eeho.fa.us2.oraclecloud.com/hcmUI/CandidateExperience/en/sites/jobsearch/jobs"
site_number = resolve_site_number(careers_url)
print(f"Site number: {site_number}")import uuid
import requests
DOMAIN = "eeho.fa.us2.oraclecloud.com"
SITE_NUMBER = "CX_1"
LISTINGS_PATH = "/hcmRestApi/resources/latest/recruitingCEJobRequisitions"
PAGE_SIZE = 200 # the CE API caps the finder-scoped list at 200 per request
HEADERS = {
"ora-irc-cx-userid": str(uuid.uuid4()), # any UUID works for anonymous access
"ora-irc-language": "en",
"content-type": "application/vnd.oracle.adf.resourceitem+json;charset=utf-8",
}
def build_listings_url(domain: str, site_number: str, offset: int, limit: int = PAGE_SIZE) -> str:
# limit/offset MUST be inside the finder; the finder's ';' '=' ',' are literal.
finder = f"findReqs;siteNumber={site_number},limit={limit},offset={offset}"
expand = (
"requisitionList.workLocation,requisitionList.otherWorkLocations,"
"requisitionList.secondaryLocations,flexFieldsFacet.values,"
"requisitionList.requisitionFlexFields"
)
query = f"onlyData=true&expand={expand}&finder={finder}"
return f"https://{domain}{LISTINGS_PATH}?{query}"
url = build_listings_url(DOMAIN, SITE_NUMBER, offset=0)
data = requests.get(url, headers=HEADERS, timeout=30).json()
# The response holds a single "search" item; the jobs live in requisitionList.
search_item = data["items"][0]
jobs = search_item.get("requisitionList", [])
total = search_item.get("TotalJobsCount", 0)
print(f"Fetched {len(jobs)} of {total} jobs")def build_job_url(domain: str, site_path: str, job_id: str) -> str:
return f"https://{domain}/hcmUI/CandidateExperience/en/sites/{site_path}/job/{job_id}"
site_path = "jobsearch" # the /sites/<segment> from the careers URL
for job in jobs[:5]: # first 5 jobs
print({
"id": job.get("Id"),
"title": job.get("Title"),
"location": job.get("PrimaryLocation"),
"secondary_locations": [loc.get("Name") for loc in job.get("secondaryLocations", [])],
"posted_date": job.get("PostedDate"),
"short_description": (job.get("ShortDescriptionStr") or "")[:100],
"is_hot_job": job.get("HotJobFlag", False),
"is_trending": job.get("TrendingFlag", False),
"url": build_job_url(DOMAIN, site_path, job.get("Id")),
})import uuid
import requests
DETAILS_PATH = "/hcmRestApi/resources/latest/recruitingCEJobRequisitionDetails"
def get_job_details(domain: str, site_number: str, job_id: str) -> dict | None:
"""Fetch full job details from Oracle Cloud."""
# Keep the finder's '"', ';', '=' and ',' literal — do not percent-encode them.
finder = f'ById;Id="{job_id}",siteNumber={site_number}'
query = f"expand=all&onlyData=true&finder={finder}"
url = f"https://{domain}{DETAILS_PATH}?{query}"
headers = {
"ora-irc-cx-userid": str(uuid.uuid4()),
"ora-irc-language": "en",
"content-type": "application/vnd.oracle.adf.resourceitem+json;charset=utf-8",
}
data = requests.get(url, headers=headers, timeout=30).json()
items = data.get("items") or []
return items[0] if items else None
details = get_job_details(DOMAIN, SITE_NUMBER, "307750")
if details:
print({
"id": details.get("Id"),
"title": details.get("Title"),
"category": details.get("Category"),
"department": details.get("Department"),
"workplace_type": details.get("WorkplaceType"),
"description": (details.get("ExternalDescriptionStr") or "")[:200],
"qualifications": (details.get("ExternalQualificationsStr") or "")[:200],
"responsibilities": (details.get("ExternalResponsibilitiesStr") or "")[:200],
"skills": [s.get("Skill") for s in details.get("skills", [])],
"flex_fields": {f.get("Prompt"): f.get("Value") for f in details.get("requisitionFlexFields", [])},
})import time
import uuid
import requests
def fetch_all_jobs(domain: str, site_number: str, page_size: int = PAGE_SIZE) -> list:
"""Fetch every job, paginating on TotalJobsCount."""
all_jobs = []
offset = 0
total = None
while True:
url = build_listings_url(domain, site_number, offset, page_size)
data = requests.get(url, headers=HEADERS, timeout=30).json()
items = data.get("items") or []
if not items:
break
search_item = items[0]
jobs = search_item.get("requisitionList") or []
if not jobs:
break
all_jobs.extend(jobs)
if total is None:
total = search_item.get("TotalJobsCount") or 0
print(f"Fetched {len(all_jobs)} of {total} jobs...")
# Page on TotalJobsCount; the top-level hasMore is unreliable.
offset += len(jobs)
if total and offset >= total:
break
if not total and len(jobs) < page_size: # defensive stop when count is absent
break
time.sleep(0.3) # matches the scraper's 300ms inter-request delay
return all_jobs
all_jobs = fetch_all_jobs(DOMAIN, SITE_NUMBER)
print(f"Total jobs collected: {len(all_jobs)}")limit and offset are ignored as top-level query params. They must live inside the finder, e.g. finder=findReqs;siteNumber=CX_1,limit=200,offset=200. Build the finder string by hand so its ';', '=' and ',' stay unencoded.
Resolve the siteNumber first: a /sites/<CX_*> path segment is the siteNumber itself, otherwise regex the careers-page HTML for 'siteNumber', and fall back to CX_1, which most production tenants use.
The top-level hasMore flag describes the single-item outer collection and reads false even mid-board. Ignore it and page on TotalJobsCount, stopping only when offset reaches the total.
The listings API returns ShortDescriptionStr. Call recruitingCEJobRequisitionDetails with the job Id (finder=ById;Id="...",siteNumber=...) for ExternalDescriptionStr, ExternalQualificationsStr, ExternalResponsibilitiesStr, skills and flex fields.
Each tenant lives at {code}.fa.{datacenter}.oraclecloud.com, sometimes with an extra realm label (e.g. code.fa.em1.ukg.oraclecloud.com). Take the domain from the company's real careers URL rather than assuming a fixed host.
401 means the board requires authentication (likely SSO-gated with no public listings), 403 means the request was blocked, and 429 is rate limiting. Throttle to ~300ms between requests, cap concurrency, and back off on 403/429.
- 1Embed limit and offset inside the finder param — Oracle ignores them as top-level query params
- 2Resolve the siteNumber from the /sites/<CX_*> path or the careers-page HTML before any API call; fall back to CX_1
- 3Page on TotalJobsCount, never on the unreliable top-level hasMore flag
- 4Build finder query strings by hand so ';', '=', ',' and '"' stay literal (requests' params dict would encode them)
- 5Fetch full content from recruitingCEJobRequisitionDetails; listings only carry ShortDescriptionStr
- 6Throttle to ~300ms between requests with no more than ~3 concurrent detail calls to avoid 403/429
One endpoint. All Oracle Cloud jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=oracle cloud" \
-H "X-Api-Key: YOUR_KEY" Access Oracle Cloud
job data today.
One API call. Structured data. No scraping infrastructure to build or maintain — start with the free tier and scale as you grow.