All platforms

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.

Get API access
ADP MyJobs
Live
100K+jobs indexed monthly
<3haverage discovery time
1hrefresh interval
Companies using ADP MyJobs
Guitar CenterRick Case Auto GroupRocket Companies
Developer tools

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.

Data fields
  • Full HTML Job Descriptions
  • Geo-Coded Location Data
  • Multi-Location Requisitions
  • Employment Type Codes
  • Department & Org Units
  • Posting Dates & Requisition IDs
Use cases
  1. 01Enterprise Job Aggregation
  2. 02Retail Chain Hiring Trends
  3. 03Multi-Location Workforce Tracking
  4. 04HR System Integration
  5. 05Career Board Monitoring
Trusted by
Guitar CenterRick Case Auto GroupRocket Companies
DIY GUIDE

How to scrape ADP MyJobs.

Step-by-step guide to extracting jobs from ADP MyJobs-powered career pages—endpoints, authentication, and working code.

RESTintermediateNo documented limit; pace requests ~200-300ms apart to avoid 403/429 blocksAuth required

Identify the ADP platform variant

ADP operates two distinct job board platforms with different APIs. MyJobs (myjobs.adp.com) returns full details in one call, while Workforce Now (workforcenow.adp.com) requires separate detail requests. Identify the platform first to choose the correct approach.

Step 1: Identify the ADP platform variant
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: myjobs

Extract the company identifier from the URL

Each platform uses a different identifier. MyJobs uses a company slug in the URL path (e.g., 'guitarcenterexternal'), while Workforce Now uses a UUID 'cid' query parameter.

Step 2: Extract the company identifier from the URL
from 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')}")

Fetch the MyJobs career-site configuration

For MyJobs, fetch the career-site configuration to obtain the authentication token (myJobsToken) and the organization OID (orgoid). Both are company-specific and needed for the listings API.

Step 3: Fetch the MyJobs career-site configuration
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]}...")

Fetch MyJobs listings with full details

MyJobs returns complete descriptions in a single call. Send the myjobstoken and rolecode headers — the my.adp.com endpoint returns an empty jobRequisitions array without rolecode. Use a plain client (no browser TLS impersonation); ADP's WAF returns count:0 to browser-like fingerprints. Page with the OData $top/$skip parameters.

Step 4: Fetch MyJobs listings with full details
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")

Fetch Workforce Now listings and details

Workforce Now requires two calls: fetch listings (without descriptions), then fetch details per job. Extract the ExternalJobID from customFieldGroup.stringFields (match the codeValue case-insensitively) and use it for the detail request.

Step 5: Fetch Workforce Now listings and details
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")

Parse and structure the job data

Extract the key fields from the API responses. Handle multi-location jobs, HTML descriptions, and missing fields gracefully. MyJobs provides the richest location data, including geo-coordinates.

Step 6: Parse and structure the job data
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']}")
Common issues
criticalTwo platform variants with incompatible APIs

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.

highMyJobs listings return an empty array without the rolecode header

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.

highWAF returns count:0 for browser-like TLS fingerprints

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.

highMissing authentication token for MyJobs

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.

highCompany identifier not found (404)

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.

mediumToken expiration during long scraping sessions

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.

mediumBlocking and rate limiting (403 / 429)

ADP returns 403 (blocked) or 429 (rate limited) under load. Space requests ~200-300ms apart and back off before retrying.

mediumWorkforce Now job missing the ExternalJobID field

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.

lowEmpty requisitionLocations array

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.

Best practices
  1. 1Detect the platform variant (MyJobs vs Workforce Now) from the URL host before choosing an approach — they use different APIs.
  2. 2Send rolecode: manager (and orgoid) on MyJobs listings calls, or the endpoint returns an empty array.
  3. 3Use a plain HTTP client without browser TLS impersonation — ADP's WAF answers count:0 to browser-like fingerprints.
  4. 4Cache the myJobsToken from the career-site config and reuse it across every paginated request.
  5. 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.
  6. 6Pace requests ~200-300ms apart and back off on 403/429 responses.
Or skip the complexity

One endpoint. All ADP MyJobs jobs. No scraping, no sessions, no maintenance.

Get API access
cURL
curl "https://connect.jobo.world/api/jobs?sources=adp myjobs" \
  -H "X-Api-Key: YOUR_KEY"
Ready to integrate

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.

99.9%API uptime
<200msAvg response
50M+Jobs processed