All platforms

Paycom Jobs API.

Pull structured job data - titles, salary ranges, locations, and full HTML descriptions - from any Paycom-hosted career board through its JWT-authenticated REST API.

Get API access
Paycom
Live
60K+jobs indexed monthly
<3haverage discovery time
1hrefresh interval
Companies using Paycom
Salvation Army of North TexasRegency Integrated Health ServicesCantex
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 Paycom.

Data fields
  • Full HTML Job Descriptions
  • Structured Salary Ranges
  • Employment Type & Shift
  • Multi-Location Listings
  • Education & Travel Requirements
  • Posted & Closing Dates
Use cases
  1. 01Job Board Aggregation
  2. 02Salary Benchmarking
  3. 03Location-Based Job Search
  4. 04Hiring Trend Analysis
Trusted by
Salvation Army of North TexasRegency Integrated Health ServicesCantex
DIY GUIDE

How to scrape Paycom.

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

RESTadvancedNo published limit; keep ~3 concurrent detail fetches with ~200ms delays to avoid 403/429Auth required

Extract the portal key from the career page URL

Each Paycom company has a unique 32-character hexadecimal portal key in their career page URL. Extract this key to identify the company's job board.

Step 1: Extract the portal key from the career page URL
import re

career_url = "https://www.paycomonline.net/v4/ats/web.php/portal/A7EA16ADA834B1F889CBB30B17AEB9A9/career-page"

# Extract the 32-character hex portal key
match = re.search(r'portal/([A-F0-9]+)/career-page', career_url)
if match:
    portal_key = match.group(1)
    print(f"Portal Key: {portal_key}")

Fetch the career page and extract the JWT token

The Paycom API requires a JWT bearer token for authentication. This token is embedded in the HTML of the career page and must be extracted with a regex before making API requests - Paycom wraps it in double quotes, single quotes, backticks, or after an '=', so try each delimiter.

Step 2: Fetch the career page and extract the JWT token
import re
import requests

career_url = "https://www.paycomonline.net/v4/ats/web.php/portal/A7EA16ADA834B1F889CBB30B17AEB9A9/career-page"

# Fetch the career page HTML
response = requests.get(career_url)
html = response.text

# Extract JWT token from the embedded JavaScript
jwt_match = re.search(r'"(eyJ[A-Za-z0-9_-]+.[A-Za-z0-9_-]+.[A-Za-z0-9_-]+)"', html)
if jwt_match:
    jwt_token = jwt_match.group(1)
    print(f"JWT Token: {jwt_token[:50]}...")
else:
    print("JWT token not found in page")

Fetch job listings from the search API

Use the job-posting-previews search endpoint to retrieve job listings. Send the token as an 'Authorization: Bearer' header. The API supports pagination via skip/take parameters and returns truncated descriptions in the preview.

Step 3: Fetch job listings from the search API
import requests

jwt_token = "YOUR_JWT_TOKEN"
url = "https://portal-applicant-tracking.us-cent.paycomonline.net/api/ats/job-posting-previews/search"

headers = {
    "authorization": f"Bearer {jwt_token}",
    "content-type": "application/json",
    "locale": "en-US",
    "origin": "https://www.paycomonline.net",
    "referer": "https://www.paycomonline.net/",
}

payload = {
    "skip": 0,
    "take": 100,  # Fetch up to 100 jobs per request
    "filtersForQuery": {
        "distanceFrom": 0,
        "workEnvironments": [],
        "positionTypes": [],
        "educationLevels": [],
        "categories": [],
        "travelTypes": [],
        "shiftTypes": [],
        "otherFilters": [],
        "keywordSearchText": "",
        "location": "",
        "sortOption": ""
    }
}

response = requests.post(url, headers=headers, json=payload)
data = response.json()

print(f"Total jobs: {data.get('jobPostingPreviewsCount', 0)}")
for job in data.get('jobPostingPreviews', []):
    print(f"  - [{job['jobId']}] {job['jobTitle']} ({job['locations']})")

Fetch full job details for each listing

The listings API returns truncated descriptions. To get the full job description and qualifications, make a separate GET request to the job-postings endpoint for each job ID.

Step 4: Fetch full job details for each listing
import requests

jwt_token = "YOUR_JWT_TOKEN"
job_id = 216798
portal_key = "A7EA16ADA834B1F889CBB30B17AEB9A9"

url = f"https://portal-applicant-tracking.us-cent.paycomonline.net/api/ats/job-postings/{job_id}"

headers = {
    "authorization": f"Bearer {jwt_token}",
    "content-type": "application/json",
    "locale": "en-US",
    "origin": "https://www.paycomonline.net",
    "referer": "https://www.paycomonline.net/",
}

response = requests.get(url, headers=headers)
data = response.json()

job = data.get("jobPosting", {})
print({
    "id": job.get("jobId"),
    "title": job.get("jobTitle"),
    "location": job.get("location"),
    "position_type": job.get("positionType"),
    "remote_type": job.get("remoteType"),
    "salary_range": job.get("salaryRange"),
    "is_hot_job": job.get("isHotJob"),
    "apply_available": job.get("applyAvailable"),
    "description_length": len(job.get("description", "")),
})

# Construct the job URL for reference
job_url = f"https://www.paycomonline.net/v4/ats/web.php/portal/{portal_key}/jobs/{job_id}"
print(f"Job URL: {job_url}")

Handle pagination for large job boards

For companies with more than 100 jobs, advance the skip parameter to page through all results. Stop when the collected count reaches jobPostingPreviewsCount or a page returns no records.

Step 5: Handle pagination for large job boards
import requests
import time

jwt_token = "YOUR_JWT_TOKEN"
url = "https://portal-applicant-tracking.us-cent.paycomonline.net/api/ats/job-posting-previews/search"

headers = {
    "authorization": f"Bearer {jwt_token}",
    "content-type": "application/json",
    "locale": "en-US",
    "origin": "https://www.paycomonline.net",
    "referer": "https://www.paycomonline.net/",
}

all_jobs = []
skip = 0
take = 100

while True:
    payload = {
        "skip": skip,
        "take": take,
        "filtersForQuery": {
            "distanceFrom": 0,
            "workEnvironments": [],
            "positionTypes": [],
            "educationLevels": [],
            "categories": [],
            "travelTypes": [],
            "shiftTypes": [],
            "otherFilters": [],
            "keywordSearchText": "",
            "location": "",
            "sortOption": ""
        }
    }

    response = requests.post(url, headers=headers, json=payload)
    data = response.json()

    jobs = data.get("jobPostingPreviews", [])
    all_jobs.extend(jobs)

    total_count = data.get("jobPostingPreviewsCount", 0)
    print(f"Fetched {len(jobs)} jobs (total: {len(all_jobs)}/{total_count})")

    if len(all_jobs) >= total_count or len(jobs) == 0:
        break

    skip += take
    time.sleep(0.5)  # Rate limiting delay

print(f"\nTotal jobs collected: {len(all_jobs)}")

Handle JWT token expiration and refresh

Paycom JWTs expire after roughly two hours. For long-running sessions, refresh the token by re-fetching the career page when the API returns a 401, then retry the request once with the fresh token.

Step 6: Handle JWT token expiration and refresh
import re
import requests

def get_fresh_jwt_token(portal_key: str) -> str:
    """Fetch career page and extract a fresh JWT token."""
    career_url = f"https://www.paycomonline.net/v4/ats/web.php/portal/{portal_key}/career-page"
    response = requests.get(career_url, timeout=15)
    response.raise_for_status()

    jwt_match = re.search(r'"(eyJ[A-Za-z0-9_-]+.[A-Za-z0-9_-]+.[A-Za-z0-9_-]+)"', response.text)
    if not jwt_match:
        raise ValueError("JWT token not found in page")
    return jwt_match.group(1)

def fetch_jobs_with_retry(portal_key: str, max_retries: int = 2) -> list:
    """Fetch jobs with automatic token refresh on 401 errors."""
    jwt_token = get_fresh_jwt_token(portal_key)
    url = "https://portal-applicant-tracking.us-cent.paycomonline.net/api/ats/job-posting-previews/search"

    headers = {
        "authorization": f"Bearer {jwt_token}",
        "content-type": "application/json",
        "locale": "en-US",
        "origin": "https://www.paycomonline.net",
        "referer": "https://www.paycomonline.net/",
    }

    for attempt in range(max_retries + 1):
        response = requests.post(url, headers=headers, json={"skip": 0, "take": 100, "filtersForQuery": {}})

        if response.status_code == 401:
            print(f"Token expired, refreshing (attempt {attempt + 1})...")
            jwt_token = get_fresh_jwt_token(portal_key)
            headers["authorization"] = f"Bearer {jwt_token}"
            continue

        response.raise_for_status()
        return response.json().get("jobPostingPreviews", [])

    raise Exception("Failed to fetch jobs after token refresh")

# Usage
portal_key = "A7EA16ADA834B1F889CBB30B17AEB9A9"
jobs = fetch_jobs_with_retry(portal_key)
print(f"Retrieved {len(jobs)} jobs")
Common issues
criticalJWT token extraction fails

The token is embedded in the career-page HTML and can be wrapped in double quotes, single quotes, backticks, or preceded by '='. Try each delimiter around the eyJ... pattern; if none match, the page markup has changed and you should re-inspect the HTML source.

highAPI returns 401 Unauthorized

Paycom JWTs have a roughly two-hour TTL. On a 401, discard the cached token, re-fetch the career page for a fresh one, and retry the request once before treating it as an auth failure.

highDeleted portal returns a page instead of a 404

A removed job board still responds HTTP 200 with a 'job portal does not exist' (JobsNotExist) marker rather than a 404. Detect that marker and stop, otherwise you may wrongly flag every job on the board as removed.

medium403 Forbidden or 429 Too Many Requests

Space requests (~200ms) and cap concurrency at around 3 parallel detail fetches. A missing or incorrect origin header can also trigger a 403, so keep the origin and referer headers set.

mediumListing descriptions are truncated

The search endpoint returns preview text only. Call the job-postings/{jobId} endpoint per job to get the full HTML description and qualifications fields.

lowJobs span multiple locations

Parse the semicolon-separated location/locations string and the secondaryLocations array. Some records only expose locations inside the embedded Google Jobs JSON (jobLocation), so fall back to it when the primary fields are empty.

Best practices
  1. 1Extract a fresh JWT at the start of each run and refresh it on any 401
  2. 2Send 'Authorization: Bearer <jwt>' with 'Content-Type: application/json' on every API call
  3. 3Use take=100 and advance the skip offset to page through job-posting-previews/search
  4. 4Fetch job-postings/{jobId} per job for full description, qualifications, and salary
  5. 5Cap concurrency (~3 parallel) with ~200ms delays to avoid 403/429 responses
  6. 6Detect the 'job portal does not exist' marker before treating jobs as removed
Or skip the complexity

One endpoint. All Paycom jobs. No scraping, no sessions, no maintenance.

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

Access Paycom
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