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.
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.
- Full HTML Job Descriptions
- Structured Salary Ranges
- Employment Type & Shift
- Multi-Location Listings
- Education & Travel Requirements
- Posted & Closing Dates
- 01Job Board Aggregation
- 02Salary Benchmarking
- 03Location-Based Job Search
- 04Hiring Trend Analysis
How to scrape Paycom.
Step-by-step guide to extracting jobs from Paycom-powered career pages—endpoints, authentication, and working code.
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}")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")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']})")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}")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)}")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")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.
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.
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.
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.
The search endpoint returns preview text only. Call the job-postings/{jobId} endpoint per job to get the full HTML description and qualifications fields.
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.
- 1Extract a fresh JWT at the start of each run and refresh it on any 401
- 2Send 'Authorization: Bearer <jwt>' with 'Content-Type: application/json' on every API call
- 3Use take=100 and advance the skip offset to page through job-posting-previews/search
- 4Fetch job-postings/{jobId} per job for full description, qualifications, and salary
- 5Cap concurrency (~3 parallel) with ~200ms delays to avoid 403/429 responses
- 6Detect the 'job portal does not exist' marker before treating jobs as removed
One endpoint. All Paycom jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=paycom" \
-H "X-Api-Key: YOUR_KEY" 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.