All platforms

Careerpuck Jobs API.

Pull every active role from a single unauthenticated JSON call per company job board, with full HTML descriptions, departments, work type, and the underlying source ATS all returned in one response.

Get API access
Careerpuck
Live
20K+jobs indexed monthly
<3haverage discovery time
1hrefresh interval
Companies using Careerpuck
EarnestLyftUdemy
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 Careerpuck.

Data fields
  • Full HTML Descriptions
  • Department & Team
  • Work & Workplace Type
  • Source ATS Platform & ID
  • Direct Apply URLs
  • Posted Date
Use cases
  1. 01Job Board Aggregation
  2. 02Source-ATS Attribution
  3. 03Multi-Company Job Monitoring
  4. 04Careers Page Enrichment
Trusted by
EarnestLyftUdemy
DIY GUIDE

How to scrape Careerpuck.

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

RESTbeginnerNo rate limiting observedNo auth

Discover company job boards

List every company job board CareerPuck hosts by parsing the public sitemap, then extract each company slug to drive your API calls.

Step 1: Discover company job boards
import requests
import re

sitemap_url = "https://app.careerpuck.com/sitemap.xml"
response = requests.get(sitemap_url)
pattern = r'https://app\.careerpuck\.com/job-board/([^<]+)'

companies = re.findall(pattern, response.text)
print(f"Found {len(companies)} company job boards")
print(f"Sample companies: {companies[:5]}")

Fetch all job listings

Request the public job-board endpoint to get every active job, complete with full descriptions, in a single response. The API expects origin and referer headers matching app.careerpuck.com.

Step 2: Fetch all job listings
import requests

company = "earnest"
url = f"https://api.careerpuck.com/v1/public/job-boards/{company}"
headers = {
    "accept": "*/*",
    "origin": "https://app.careerpuck.com",
    "referer": "https://app.careerpuck.com/"
}

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

jobs = data.get("jobs", [])
print(f"Found {len(jobs)} active jobs")

Parse job fields from the response

Read the fields you need from each job object. Prefer atsSourceId as the stable identifier (it originates from the source ATS), falling back to permalink, and HTML-decode the content field before storing or displaying it.

Step 3: Parse job fields from the response
import html

for job in jobs:
    # HTML-decode the content field (unescape twice if double-encoded)
    description = html.unescape(job.get("content", ""))

    print({
        "id": job.get("atsSourceId") or job.get("permalink"),
        "title": job.get("title"),
        "location": job.get("location"),
        "department": job.get("department"),
        "team": job.get("team"),
        "work_type": job.get("workType"),
        "workplace_type": job.get("workplaceType"),
        "url": job.get("publicUrl"),
        "apply_url": job.get("applyUrl"),
        "posted_at": job.get("postedAt"),
        "source_platform": job.get("atsSourcePlatform"),
        "description_html": description[:200],
    })

Enrich with the employer profile

The response carries an employerProfile object and a top-level departments list. Use them to enrich records with the company name, website, and canonical careers page URL.

Step 4: Enrich with the employer profile
employer = data.get("employerProfile", {})
departments = data.get("departments", [])

print({
    "company_name": employer.get("name") or employer.get("companyName"),
    "website": employer.get("website"),
    "career_page_url": employer.get("careerPageUrl"),
    "departments": departments,
})

Handle errors and edge cases

Wrap requests defensively: 404 means the company slug is wrong or the board was removed, 403/429 signal throttling so you should back off, and non-public statuses should be filtered out. Return an empty list rather than raising on a missing board.

Step 5: Handle errors and edge cases
import requests
import time

def fetch_careerpuck_jobs(company: str) -> list:
    url = f"https://api.careerpuck.com/v1/public/job-boards/{company}"
    headers = {
        "accept": "*/*",
        "origin": "https://app.careerpuck.com",
        "referer": "https://app.careerpuck.com/"
    }

    try:
        response = requests.get(url, headers=headers, timeout=10)
        if response.status_code == 404:
            print(f"Company '{company}' not found")
            return []
        if response.status_code in (403, 429):
            print(f"Throttled or blocked ({response.status_code}); backing off")
            time.sleep(5)
            return []
        response.raise_for_status()
        data = response.json()
        # Keep only published roles
        return [j for j in data.get("jobs", []) if j.get("status") == "public"]
    except requests.exceptions.RequestException as e:
        print(f"Request failed: {e}")
        return []

jobs = fetch_careerpuck_jobs("earnest")
Common issues
mediumHTML-encoded (sometimes double-encoded) descriptions

The 'content' field arrives HTML-encoded (e.g. &lt;div&gt;, occasionally double-encoded as &amp;lt;div&amp;gt;). Unescape it — twice if needed — before parsing or displaying.

highMissing origin/referer headers

Send 'origin: https://app.careerpuck.com' and 'referer: https://app.careerpuck.com/' on every request. Calls without these headers may be rejected.

mediumNon-public statuses in the jobs array

Only jobs with status == 'public' are live listings. Filter out any other status so drafts or closed roles do not leak into your dataset.

mediumCompany not found (404 error)

Verify the company slug taken from the job-board URL. The slug in 'app.careerpuck.com/job-board/{company}' must match the API path parameter exactly.

lowEmpty jobs array

Some boards have no active postings. Check whether 'jobs' is empty and handle it gracefully rather than treating it as an error.

lowJobs proxy to other ATS platforms

CareerPuck aggregates other ATSes, so 'atsSourcePlatform' names the origin (greenhouse, lever, etc.) and 'applyUrl' points at that ATS. Use 'atsSourceId' as the stable ID and route applicants via 'applyUrl'.

Best practices
  1. 1Use the listings endpoint for complete data — the per-job details API is only needed for extras like jobClips
  2. 2Always HTML-decode the 'content' field (twice if double-encoded) before parsing or display
  3. 3Send origin and referer headers set to app.careerpuck.com on every request
  4. 4Keep only jobs where status == 'public' and de-duplicate by atsSourceId
  5. 5Space requests (~200ms) and back off when you see 403 or 429
  6. 6Cache per company and refresh daily — boards change slowly and return all jobs at once
Or skip the complexity

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

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

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