CareerPlug Jobs API.
Aggregate hourly, franchise, and small-business openings from CareerPlug's server-rendered subdomain boards, each record carrying structured pay ranges, State-City-ZIP locations, and full job descriptions.
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 CareerPlug.
- Structured Pay Ranges
- City, State & ZIP Locations
- Full Job Descriptions
- Employment Type
- Job Post Dates
- Direct Apply URLs
- 01Hourly & Frontline Job Aggregation
- 02Franchise Hiring Feeds
- 03Local Job Board Ingestion
- 04SMB Recruitment Analytics
How to scrape CareerPlug.
Step-by-step guide to extracting jobs from CareerPlug-powered career pages—endpoints, authentication, and working code.
import requests
from bs4 import BeautifulSoup
company_slug = "bemobile"
url = f"https://{company_slug}.careerplug.com/jobs"
headers = {
"User-Agent": "Mozilla/5.0 (compatible; JobScraper/1.0)",
"Accept": "text/html",
}
response = requests.get(url, headers=headers, timeout=10)
response.raise_for_status()
soup = BeautifulSoup(response.text, "html.parser")
print(f"Fetched page: {response.url}")# Find the job container (supports both modern and legacy layouts)
job_container = soup.select_one("#job_table, #job-list")
if job_container:
job_links = job_container.select("div > a[href^='/jobs/']")
jobs = []
for link in job_links:
title_elem = link.select_one(".job-title .name")
location_elem = link.select_one(".job-location")
jobs.append({
"title": title_elem.get_text(strip=True) if title_elem else None,
"location": location_elem.get_text(strip=True) if location_elem else None,
"url": f"https://{company_slug}.careerplug.com{link['href']}",
"job_id": link['href'].split('/')[-1],
})
print(f"Found {len(jobs)} jobs")
for job in jobs[:3]:
print(f" - {job['title']} @ {job['location']}")def fetch_job_details(job_url: str) -> dict:
"""Fetch full job details from a CareerPlug job page."""
response = requests.get(job_url, headers=headers, timeout=10)
response.raise_for_status()
soup = BeautifulSoup(response.text, "html.parser")
# Extract job details
title = soup.select_one("h1")
description = soup.select_one(".trix-content, .job-description, [class*='description']")
location_info = soup.select_one("h1 + p, [class*='location']")
# Fall back to the application page when the description is missing
if description is None:
apply_url = job_url.rstrip("/") + "/apps/new"
apply_soup = BeautifulSoup(requests.get(apply_url, headers=headers, timeout=10).text, "html.parser")
description = apply_soup.select_one(".trix-content, .job-description, [class*='description']")
return {
"title": title.get_text(strip=True) if title else None,
"description": description.get_text(strip=True) if description else None,
"description_html": str(description) if description else None,
"location": location_info.get_text(strip=True) if location_info else None,
"url": job_url,
}
# Fetch details for the first job
if jobs:
details = fetch_job_details(jobs[0]["url"])
print(f"Title: {details['title']}")
print(f"Location: {details['location']}")
print(f"Description length: {len(details['description'] or '')} chars")import time
def fetch_all_jobs(company_slug: str, max_pages: int = 10) -> list:
"""Fetch all jobs from a CareerPlug company with pagination."""
all_jobs = []
base_url = f"https://{company_slug}.careerplug.com/jobs"
for page in range(1, max_pages + 1):
url = f"{base_url}?page={page}" if page > 1 else base_url
response = requests.get(url, headers=headers, timeout=10)
if response.status_code in (403, 404):
break # 404 = unknown company; 403 = AWS ELB throttling
soup = BeautifulSoup(response.text, "html.parser")
job_container = soup.select_one("#job_table, #job-list")
if not job_container:
break
job_links = job_container.select("div > a[href^='/jobs/']")
if not job_links:
break
for link in job_links:
title_elem = link.select_one(".job-title .name")
location_elem = link.select_one(".job-location")
all_jobs.append({
"title": title_elem.get_text(strip=True) if title_elem else None,
"location": location_elem.get_text(strip=True) if location_elem else None,
"url": f"https://{company_slug}.careerplug.com{link['href']}",
})
print(f"Page {page}: Found {len(job_links)} jobs (total: {len(all_jobs)})")
time.sleep(1) # Be respectful
return all_jobs
jobs = fetch_all_jobs("bemobile")
print(f"Total jobs collected: {len(jobs)}")CareerPlug keys each employer to its own subdomain (e.g. sir-speedy-careers.careerplug.com). Confirm the exact subdomain from the company's careers link before scraping; app.* and www.* are not employer boards.
Bursts of requests trigger an AWS ELB 403 (body contains '403 Forbidden') rather than a real block. Treat these as rate limiting: back off, retry with jitter, and keep concurrency low (~3 requests).
Do not fetch /jobs.js — it is explicitly disallowed and only returns Rails UJS DOM-replacement JavaScript, not JSON. Parse the standard HTML /jobs page instead.
Some tenants leave the /jobs/{id} detail page thin and serve the complete description on /jobs/{id}/apps/new. Fall back to the apply page when the detail body is empty.
Locations are formatted like 'NH-Keene-03431'. Split on hyphens to recover state, city, and ZIP for structured storage.
Themes differ across employers, so use fallback selectors: '#job_table, #job-list' for the container and '.trix-content, .job-description, [class*="description"]' for the body.
CareerPlug exposes no REST/GraphQL endpoint; /jobs.json and ?format=json return HTTP 406 and /api/jobs 404s. All job data must be parsed from server-rendered HTML.
- 1Fetch the server-rendered /jobs page — never /jobs.js, which robots.txt disallows
- 2Identify each employer by its careerplug.com subdomain, skipping app.* and www.*
- 3Use fallback container selectors (#job_table, #job-list) to survive theme differences
- 4Follow /jobs/{id} to /apps/new when the detail description comes back empty
- 5Throttle to ~3 concurrent requests with short delays; treat AWS ELB 403s as rate limiting and back off
- 6Split the {State}-{City}-{ZIP} location string into structured city, state, and ZIP fields
One endpoint. All CareerPlug jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=careerplug" \
-H "X-Api-Key: YOUR_KEY" Access CareerPlug
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.