All platforms

DirectEmployers / JobSync Jobs API.

Tap the JobSync Solr feed behind DirectEmployers member microsites to pull every open requisition — full descriptions, locations, and apply URLs — as clean JSON.

Get API access
DirectEmployers / JobSync
Live
<3haverage discovery time
1hrefresh interval
Companies using DirectEmployers / JobSync
Pearson
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 DirectEmployers / JobSync.

Data fields
  • Full Job Descriptions
  • Structured Location Data
  • Requisition IDs & GUIDs
  • Direct Apply URLs
  • Category, Type & Shift
  • Posted & Updated Dates
Use cases
  1. 01Enterprise Careers Aggregation
  2. 02Compliance Feed Monitoring
  3. 03Requisition Change Tracking
  4. 04Apply URL Resolution
Trusted by
Pearson
DIY GUIDE

How to scrape DirectEmployers / JobSync.

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

RESTintermediate~1 request every 2s (single-threaded); 403/429 if exceededNo auth

Fetch a page from the JobSync Solr API

Member microsites (e.g. pearson.jobs) are served by JobSync's anonymous Solr search endpoint. The X-Origin header names the microsite and scopes the results to that employer; a missing or rejected origin returns 401.

Step 1: Fetch a page from the JobSync Solr API
import requests

BASE = "https://prod-search-api.jobsyn.org/api/v1/solr/search"
HEADERS = {"Accept": "application/json", "X-Origin": "pearson.jobs"}

resp = requests.get(BASE, params={"page": 1, "num_items": 15}, headers=HEADERS, timeout=15)
resp.raise_for_status()
data = resp.json()

print(data["pagination"]["total"], "jobs total")
print(len(data["jobs"]), "jobs on this page")

Page through every listing

Follow the pagination block rather than guessing page counts: keep requesting until has_more_pages is false. Stay single-threaded with a ~2s gap between requests to avoid tripping IP or rate-limit blocks.

Step 2: Page through every listing
import time
import requests

BASE = "https://prod-search-api.jobsyn.org/api/v1/solr/search"
HEADERS = {"Accept": "application/json", "X-Origin": "pearson.jobs"}

def iter_jobs():
    page = 1
    while True:
        resp = requests.get(BASE, params={"page": page, "num_items": 15},
                             headers=HEADERS, timeout=15)
        resp.raise_for_status()
        payload = resp.json()
        yield from payload.get("jobs", [])
        if not payload.get("pagination", {}).get("has_more_pages"):
            break
        page += 1
        time.sleep(2)  # single request at a time, ~2s apart

Normalize each job record

The 32-character guid is the native job key — skip any row that lacks it. Rebuild the canonical listing URL from the title and city slabs, and append the microsite's view-site id (10 for Pearson) to form the apply redirect.

Step 3: Normalize each job record
def normalize(job):
    guid = (job.get("guid") or "").upper()
    if len(guid) != 32:
        return None  # JobSync rows without a GUID key are not real listings

    title_slug = job.get("title_slug") or "job"
    slab = (job.get("city_slab_exact") or "").split("::", 1)[0].strip("/")
    if slab.lower().endswith("/jobs"):
        slab = slab[:-5]
    location_slug = slab.replace("/", "-").lower() if slab else "global"

    return {
        "external_id": guid,
        "title": (job.get("title_exact") or "").strip(),
        "company": (job.get("company_exact") or "").strip(),
        "location": job.get("location_exact"),
        "description": job.get("description"),
        "requisition_id": job.get("reqid"),
        "posted_at": job.get("date_new"),
        "updated_at": job.get("date_updated"),
        "listing_url": f"https://pearson.jobs/{location_slug}/{title_slug}/{guid}/job/",
        "apply_url": f"https://rr.jobsyn.org/{guid}10",  # 10 = Pearson view-site id (vsid)
    }

Handle origin, block, and rate-limit responses

Distinguish the three failure modes JobSync returns: 401 means the X-Origin header is missing or rejected, 403 means the egress IP is blocked, and 429 means you are being throttled. Back off on 403/429 and keep the crawl serial.

Step 4: Handle origin, block, and rate-limit responses
from time import sleep
import requests

BASE = "https://prod-search-api.jobsyn.org/api/v1/solr/search"
HEADERS = {"Accept": "application/json", "X-Origin": "pearson.jobs"}

def fetch_page(page, retries=3):
    for attempt in range(retries):
        resp = requests.get(BASE, params={"page": page, "num_items": 15},
                            headers=HEADERS, timeout=15)
        if resp.status_code == 401:
            raise RuntimeError("X-Origin header missing or rejected by JobSync")
        if resp.status_code in (403, 429):
            sleep(5 * (attempt + 1))  # IP blocked or rate limited — back off
            continue
        resp.raise_for_status()
        return resp.json()
    raise RuntimeError(f"JobSync kept throttling page {page}")
Common issues
high401 without the X-Origin header

JobSync requires an X-Origin header naming the member microsite (e.g. 'pearson.jobs'). Send it on every request; without it the Solr API rejects the call and the feed is empty.

high403 from blocked egress IPs

Datacenter or aggressive IPs get blocked. Crawl single-threaded, keep ~2s between requests, and route through residential or rotating egress if a fixed IP starts returning 403.

medium429 rate limiting under concurrency

The endpoint expects one request at a time. Serialize page fetches with a ~2 second delay and add exponential backoff on 429 instead of retrying immediately.

mediumRows missing a 32-character guid

Some Solr rows omit the guid key and are not resolvable jobs. Treat guid as the native job identity and drop any row whose guid is absent or not exactly 32 characters.

mediumBroken apply links

The rr.jobsyn.org redirect needs the microsite's view-site id appended to the guid (10 for Pearson). Using the guid alone or the wrong vsid produces an invalid apply URL.

Best practices
  1. 1Always send X-Origin set to the member microsite host (e.g. pearson.jobs).
  2. 2Crawl single-threaded with ~2 seconds between requests to avoid 403/429.
  3. 3Use the 32-character guid as the stable job key; skip rows that lack it.
  4. 4Descriptions ship inline with listings — no separate detail request is needed.
  5. 5Follow pagination.has_more_pages instead of guessing total_pages.
  6. 6Keep reqid and buid as metadata; the guid, not reqid, is the native key.
Or skip the complexity

One endpoint. All DirectEmployers / JobSync jobs. No scraping, no sessions, no maintenance.

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

Access DirectEmployers / JobSync
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