ADP MyJobs Jobs API.
Pull structured jobs from both ADP career-site variants — MyJobs and Workforce Now — via public JSON APIs, returning full HTML descriptions, geo-coded locations, and OData-style pagination.
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 ADP MyJobs.
- Full HTML Job Descriptions
- Geo-Coded Location Data
- Multi-Location Requisitions
- Employment Type Codes
- Department & Org Units
- Posting Dates & Requisition IDs
- 01Enterprise Job Aggregation
- 02Retail Chain Hiring Trends
- 03Multi-Location Workforce Tracking
- 04HR System Integration
- 05Career Board Monitoring
How to scrape ADP MyJobs.
Step-by-step guide to extracting jobs from ADP MyJobs-powered career pages—endpoints, authentication, and working code.
import re
from urllib.parse import urlparse
def identify_adp_platform(url: str) -> str:
"""Identify which ADP platform a URL belongs to."""
if 'workforcenow.adp.com' in url:
return 'workforce-now'
elif 'myjobs.adp.com' in url:
return 'myjobs'
return 'unknown'
# Test the function
url = "https://myjobs.adp.com/guitarcenterexternal/cx"
print(f"Platform: {identify_adp_platform(url)}") # Output: myjobsfrom urllib.parse import urlparse, parse_qs
def extract_company_id(url: str, platform: str) -> str:
"""Extract company identifier based on platform type."""
parsed = urlparse(url)
if platform == 'myjobs':
# Extract from path: /guitarcenterexternal/cx
path_parts = parsed.path.strip('/').split('/')
return path_parts[0] if path_parts else None
elif platform == 'workforce-now':
# Extract cid from query parameters (UUID format)
params = parse_qs(parsed.query)
return params.get('cid', [None])[0]
return None
# MyJobs example
url1 = "https://myjobs.adp.com/guitarcenterexternal/cx"
print(f"MyJobs company: {extract_company_id(url1, 'myjobs')}")
# Workforce Now example
url2 = "https://workforcenow.adp.com/mascsr/default/mdf/recruitment/recruitment.html?cid=1edfe7b1-c2a4-4b67-921f-8b32dfaed4bb"
print(f"Workforce Now cid: {extract_company_id(url2, 'workforce-now')}")import requests
def get_myjobs_config(company_id: str) -> dict:
"""Fetch career-site configuration and authentication token."""
url = f"https://myjobs.adp.com/public/staffing/v1/career-site/{company_id}"
response = requests.get(url, timeout=30)
if response.status_code == 404:
raise ValueError(f"Company '{company_id}' not found")
response.raise_for_status()
config = response.json()
return {
"domain": config.get("domain"),
"client_name": config.get("clientName"),
"orgoid": config.get("orgoid"),
"myjobs_token": config.get("myJobsToken"),
}
# Example usage
config = get_myjobs_config("guitarcenterexternal")
print(f"Company: {config['client_name']}")
print(f"Token preview: {config['myjobs_token'][:50]}...")import requests
import time
def fetch_myjobs_listings(company_id: str, token: str, orgoid: str = None, page_size: int = 100) -> list:
"""Fetch all job listings with full details from MyJobs API."""
url = "https://my.adp.com/myadp_prefix/mycareer/public/staffing/v1/job-requisitions/apply-custom-filters"
headers = {
"accept": "application/json, text/plain, */*",
"origin": "https://myjobs.adp.com",
"referer": "https://myjobs.adp.com/",
"myjobstoken": token,
# Without rolecode the endpoint returns an empty jobRequisitions array.
"rolecode": "manager",
}
if orgoid:
headers["orgoid"] = orgoid
all_jobs = []
skip = 0
while True:
params = {
"$orderby": "postingDate desc",
"$select": "reqId,jobTitle,publishedJobTitle,type,jobDescription,jobQualifications,workLevelCode,clientRequisitionID,postingDate,requisitionLocations",
"$top": page_size,
"$skip": skip,
"tz": "America/New_York",
}
response = requests.get(url, headers=headers, params=params, timeout=30)
response.raise_for_status()
data = response.json()
total = data.get("count", 0)
jobs = data.get("jobRequisitions", [])
# count>0 with an empty page signals a transient/WAF response, not "no jobs".
if not jobs:
break
all_jobs.extend(jobs)
print(f"Fetched {len(all_jobs)} of {total} jobs")
if len(jobs) < page_size or len(all_jobs) >= total:
break
skip += page_size
time.sleep(0.3) # Space requests to avoid throttling
return all_jobs
# Example usage
jobs = fetch_myjobs_listings(
"guitarcenterexternal", config["myjobs_token"], config.get("orgoid")
)
print(f"Retrieved {len(jobs)} total jobs")import requests
import time
def fetch_workforcenow_jobs(cid: str) -> list:
"""Fetch jobs from Workforce Now (listings + details per job)."""
base_url = "https://workforcenow.adp.com/mascsr/default/careercenter/public/events/staffing/v1"
headers = {
"accept": "application/json",
"content-type": "application/json",
"locale": "en_US",
}
# Step 1: Fetch listings
listings_url = f"{base_url}/job-requisitions"
params = {"cid": cid, "lang": "en_US", "locale": "en_US", "$top": 1000}
response = requests.get(listings_url, headers=headers, params=params, timeout=30)
response.raise_for_status()
data = response.json()
jobs = []
for req in data.get("jobRequisitions", []):
# Extract ExternalJobID from custom fields (codeValue is case-insensitive)
external_id = None
for field in req.get("customFieldGroup", {}).get("stringFields", []):
if (field.get("nameCode", {}).get("codeValue") or "").lower() == "externaljobid":
external_id = field.get("stringValue")
break
if not external_id:
continue
# Step 2: Fetch job details
detail_url = f"{base_url}/job-requisitions/{external_id}"
detail_resp = requests.get(
detail_url,
headers=headers,
params={"cid": cid, "lang": "en_US", "locale": "en_US"},
timeout=30,
)
if detail_resp.ok:
detail = detail_resp.json()
jobs.append({
"id": external_id,
"title": detail.get("requisitionTitle"),
"description_html": detail.get("requisitionDescription"),
"employment_type": detail.get("workLevelCode", {}).get("shortName"),
"posting_date": detail.get("postDate"),
"url": f"https://workforcenow.adp.com/mascsr/default/mdf/recruitment/recruitment.html?cid={cid}&jobId={external_id}",
})
time.sleep(0.2) # Space detail requests
return jobs
# Example usage
jobs = fetch_workforcenow_jobs("1edfe7b1-c2a4-4b67-921f-8b32dfaed4bb")
print(f"Retrieved {len(jobs)} jobs with full details")def parse_myjobs_job(job: dict, company_id: str) -> dict:
"""Parse a MyJobs listing into structured format."""
locations = job.get("requisitionLocations", [])
primary_loc = next((l for l in locations if l.get("primaryIndicator")), locations[0] if locations else {})
address = primary_loc.get("address", {})
return {
"id": job.get("reqId"),
"title": job.get("publishedJobTitle") or job.get("jobTitle"),
"description_html": job.get("jobDescription"),
"qualifications_html": job.get("jobQualifications"),
"employment_type": job.get("workLevelCode"),
"posting_date": job.get("postingDate"),
"location": {
"city": address.get("cityName"),
"state": address.get("countrySubdivisionLevel1", {}).get("codeValue"),
"postal_code": address.get("postalCode"),
"address": address.get("lineOne"),
"coordinates": address.get("geoCoordinate"),
},
"url": f"https://myjobs.adp.com/{company_id}/cx/job/{job.get('reqId')}",
"easy_apply": job.get("easyApplyEnabled", False),
}
# Parse and display sample jobs
for job in jobs[:3]:
parsed = parse_myjobs_job(job, "guitarcenterexternal")
print(f"- {parsed['title']} ({parsed['employment_type']})")
if parsed['location'].get('city'):
print(f" Location: {parsed['location']['city']}, {parsed['location']['state']}")
print(f" URL: {parsed['url']}")Detect the platform from the URL host before scraping. MyJobs (myjobs.adp.com) returns full details in the listings call; Workforce Now (workforcenow.adp.com) needs a separate detail request per job.
ADP moved job-requisitions to my.adp.com, and that endpoint returns an empty jobRequisitions array unless you send the rolecode: manager header. Always include it (plus orgoid from the career-site config) on the listings request.
ADP's WAF silently returns {"count":0,"jobRequisitions":[]} when it detects browser-like TLS impersonation on the public staffing endpoints. Use a plain HTTP client (e.g. requests) without any browser TLS-impersonation profile.
MyJobs requires the myJobsToken from the career-site config endpoint before any listings call. Fetch /public/staffing/v1/career-site/{companyId} first and cache the token for the whole run.
Verify the identifier from the real careers URL. For MyJobs it is the first path segment before /cx; for Workforce Now it is the cid UUID in the query string. Direct /cx/job detail links are not valid entry points.
The myJobsToken can lapse mid-run. On a 401 response, re-fetch the career-site config to obtain a fresh token and resume from your last pagination offset.
ADP returns 403 (blocked) or 429 (rate limited) under load. Space requests ~200-300ms apart and back off before retrying.
Some Workforce Now requisitions lack an ExternalJobID in customFieldGroup.stringFields. Match the codeValue case-insensitively, and skip jobs with no usable ID rather than calling the detail API with a blank path segment.
Many jobs ship with empty or address-less location arrays. Fall back to the location name/alias, then to the country code, and finally parse the description HTML before treating a job as location-less.
- 1Detect the platform variant (MyJobs vs Workforce Now) from the URL host before choosing an approach — they use different APIs.
- 2Send rolecode: manager (and orgoid) on MyJobs listings calls, or the endpoint returns an empty array.
- 3Use a plain HTTP client without browser TLS impersonation — ADP's WAF answers count:0 to browser-like fingerprints.
- 4Cache the myJobsToken from the career-site config and reuse it across every paginated request.
- 5Treat count:0 with an empty array as an authoritative 'no jobs', but count>0 with an empty page as a transient error to retry.
- 6Pace requests ~200-300ms apart and back off on 403/429 responses.
One endpoint. All ADP MyJobs jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=adp myjobs" \
-H "X-Api-Key: YOUR_KEY" Access ADP MyJobs
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.