GR8 People Jobs API.
Pull complete enterprise job listings — full HTML descriptions, Schema.org pay ranges, and requisition data — from a single career-site GraphQL call, with no HTML parsing or browser rendering required.
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 GR8 People.
- Full HTML Job Descriptions
- Structured Pay Ranges & Currency
- Schema.org JobPosting Data
- Requisition Numbers & Job Keys
- Workplace & Position Types
- Location & Remote Flags
- 01Enterprise Job Aggregation
- 02Compensation Benchmarking
- 03Talent Market Intelligence
- 04Recruiting Data Pipelines
How to scrape GR8 People.
Step-by-step guide to extracting jobs from GR8 People-powered career pages—endpoints, authentication, and working code.
# GR8 People tenants are the full career-site host itself.
# These vendor hosts are NOT tenants and must be skipped.
RESERVED_HOSTS = {
"www.gr8people.com", "api.gr8people.com", "assets.gr8people.com",
"demo.gr8people.com", "help.gr8people.com", "status.gr8people.com",
"support.gr8people.com", "t.gr8people.com",
}
def is_tenant(host: str) -> bool:
host = host.lower()
if host == "gr8people.randstad.co.uk": # verified branded host
return True
return host.endswith(".gr8people.com") and host not in RESERVED_HOSTS
tenant = "lambweston.gr8people.com" # e.g. Lamb Weston, T. Rowe Price
assert is_tenant(tenant)
board_url = f"https://{tenant}/jobs"
graphql_url = f"https://{tenant}/graphql"
print(board_url, graphql_url)import requests
tenant = "lambweston.gr8people.com"
graphql_url = f"https://{tenant}/graphql"
payload = {
"operationName": "searchJobs",
"variables": {"query": "", "start": 0, "first": 100, "filters": {}},
"extensions": {"trustedDocument": {"id": "search-jobs"}},
}
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"Origin": f"https://{tenant}",
"Referer": f"https://{tenant}/jobs",
}
resp = requests.post(graphql_url, json=payload, headers=headers, timeout=30)
resp.raise_for_status()
body = resp.json()
if body.get("errors"):
raise RuntimeError(f"GraphQL errors: {body['errors']}")
results = body["data"]["searchJobs"]["results"]
print("total:", results["totalCount"], "this page:", len(results["nodes"]))import requests
import time
tenant = "troweprice.gr8people.com"
graphql_url = f"https://{tenant}/graphql"
headers = {
"Accept": "application/json",
"Content-Type": "application/json",
"Origin": f"https://{tenant}",
"Referer": f"https://{tenant}/jobs",
}
PAGE_SIZE = 100
def fetch_page(start):
payload = {
"operationName": "searchJobs",
"variables": {"query": "", "start": start, "first": PAGE_SIZE, "filters": {}},
"extensions": {"trustedDocument": {"id": "search-jobs"}},
}
resp = requests.post(graphql_url, json=payload, headers=headers, timeout=30)
resp.raise_for_status()
return resp.json()["data"]["searchJobs"]["results"]
all_jobs, start = [], 0
while True:
results = fetch_page(start)
nodes, total = results["nodes"], results["totalCount"]
if not nodes:
if start < total:
raise RuntimeError(f"Empty page at {start} while {total} jobs reported")
break
all_jobs.extend(nodes)
start += len(nodes)
if start >= total or not results["pageInfo"]["hasNextPage"]:
break
time.sleep(2) # single in-flight request, ~2s apart
print(f"Fetched {len(all_jobs)} of {total} jobs")import json
def slugify(title: str) -> str:
cleaned = "".join(c if (c.isalnum() or c == " ") else " " for c in title.lower())
return "-".join(cleaned.split()) or "job"
def parse_job(node, tenant):
key = (node.get("key") or "").strip()
title = (node.get("title") or "").strip()
description = (node.get("descriptionHTML") or "").strip()
# GR8 People routes only expose numeric job keys.
if not key.isdigit() or not title or not description:
return None
listing_url = f"https://{tenant}/jobs/{key}/{slugify(title)}"
structured = {}
if node.get("structuredDataJSON"):
try:
structured = json.loads(node["structuredDataJSON"])
except json.JSONDecodeError:
structured = {}
return {
"external_id": key, # native route key
"requisition_number": node.get("number"), # metadata only, NOT a URL key
"title": title,
"description_html": description,
"listing_url": listing_url,
"apply_url": f"{listing_url}/apply",
"company": (structured.get("hiringOrganization") or {}).get("name"),
"posted_at": structured.get("datePosted") or node.get("postedOn"),
"closes_at": structured.get("validThrough"),
"workplace_type": node.get("workplaceType"),
"pay_range": {
"low": node.get("payRangeLow"),
"mid": node.get("payRangeMid"),
"high": node.get("payRangeHigh"),
"currency": node.get("currencyCode"),
},
}
jobs = [j for n in all_jobs if (j := parse_job(n, tenant))]
print(f"Mapped {len(jobs)} jobs")Hosts like www, api, assets, demo, help, status, support, and t under gr8people.com are marketing/infrastructure, not career boards. Only treat other *.gr8people.com subdomains (or the verified gr8people.randstad.co.uk host) as tenants; posting searchJobs to a reserved host returns no board.
The endpoint is a persisted-query contract, so a raw ad-hoc GraphQL body is refused. Always send operationName 'searchJobs' together with extensions.trustedDocument.id set to 'search-jobs'.
A page returning zero nodes while start is still below results.totalCount signals an incomplete snapshot, not the end of the board. Only stop once start reaches totalCount, and retry the empty page rather than accepting a short result.
Some tenants block requests that do not look like the career site. Send the Accept, Origin, and Referer headers, keep a single in-flight request roughly 2 seconds apart, and back off on 403 (blocked) and 429 (rate limited).
node.number is the requisition and differs from node.key, the numeric route key. Build /jobs/{key}/{slug} and apply URLs from node.key, and keep node.number as metadata only.
- 1Use the numeric node.key for external IDs and URL building; store node.number as requisition metadata only.
- 2Always send operationName 'searchJobs' plus extensions.trustedDocument.id 'search-jobs' on every request.
- 3Page at first=100 and trust results.totalCount over pageInfo.hasNextPage when the two disagree.
- 4Throttle to a single in-flight request roughly 2 seconds apart and back off on 403 and 429.
- 5Prefer structuredDataJSON (Schema.org JobPosting) for company, dates, and locations, falling back to primaryPlace and places.
- 6Skip nodes missing a numeric key or descriptionHTML instead of emitting partial records.
One endpoint. All GR8 People jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=gr8 people" \
-H "X-Api-Key: YOUR_KEY" Access GR8 People
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.