AllHires Jobs API.
Extract structured vacancies from AllHires-hosted career boards — practice group, PQE level, salary, and UK closing dates included — through a single public JSON API with no account 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 AllHires.
- Practice Group & Department
- PQE & Experience Level
- Salary & Employment Type
- Full HTML Descriptions
- UK Closing Dates
- Structured Locations
- 01Legal Recruitment Aggregation
- 02Law Firm Vacancy Tracking
- 03Job Board Syndication
- 04Salary & PQE Benchmarking
How to scrape AllHires.
Step-by-step guide to extracting jobs from AllHires-powered career pages—endpoints, authentication, and working code.
import time
import requests
from bs4 import BeautifulSoup
TENANT = "lewissilkin" # first label of {tenant}.allhires.com
ORIGIN = f"https://{TENANT}.allhires.com"
session = requests.Session()
# 1. Load the public board so AllHires issues the anti-CSRF token.
boot = session.get(f"{ORIGIN}/app/", headers={"Accept": "text/html"})
boot.raise_for_status()
# The token is served twice: a hidden <input> and a Set-Cookie.
field = BeautifulSoup(boot.text, "html.parser").find(
"input", {"name": "__RequestVerificationToken"})
request_token = field["value"].strip()
# session.cookies already holds the matching __RequestVerificationToken cookie.def api_get(url, referer):
# Every API call double-submits the token: header + cookie (from the jar).
return session.get(url, headers={
"Accept": "application/json",
"__RequestVerificationToken": request_token,
"Referer": referer,
})
# The complete public list comes back in one request — no pagination.
positions = api_get(
f"{ORIGIN}/webapi/candidate/Positions?isIntranetReq=false",
f"{ORIGIN}/app/",
).json()["Positions"]
jobs = []
for p in positions:
job_id = p.get("ApplyingForID")
title = (p.get("CandidateTitle") or "").strip()
if not job_id or job_id <= 0 or not title:
continue # skip unpublished / placeholder rows
jobs.append({
"id": job_id,
"title": title,
"department": p.get("ExternalDepartment"),
"practice_group": p.get("PracticeGroup"),
"location": p.get("LocationText"),
"pqe_required": p.get("PQERequired"),
"url": f"{ORIGIN}/app/PositionDetails?id={job_id}",
})for job in jobs:
detail = api_get(
f"{ORIGIN}/webapi/candidate/position/details/?id={job['id']}&isIntranetReq=false",
job["url"],
).json()
d = detail["Details"]
job["salary"] = d.get("Salary")
job["employment_type"] = d.get("TermType")
job["closing_date"] = d.get("ClosingDate") # UK date, e.g. 31/12/2026
# Rebuild the description from non-empty sections only.
job["description"] = "".join(
f"<h2>{(s.get('SectionHeader') or '').strip()}</h2>{s['SectionText']}"
for s in detail["Sections"]
if (s.get("SectionText") or "").strip()
)
time.sleep(0.2) # ~200ms between requestsresp = api_get(f"{ORIGIN}/webapi/candidate/Positions?isIntranetReq=false",
f"{ORIGIN}/app/")
if resp.status_code in (401, 403):
# Token rotated or session expired — re-run the bootstrap step.
raise RuntimeError("Re-bootstrap /app/ to refresh __RequestVerificationToken")
elif resp.status_code == 429:
time.sleep(5) # rate limited: back off, then retry
elif resp.status_code == 404:
raise RuntimeError("Unknown tenant — check the {tenant}.allhires.com host")AllHires requires the __RequestVerificationToken sent as both a request header and a cookie. Bootstrap /app/ to obtain both, and re-fetch them if the session starts returning 401/403.
Only scrape {tenant}.allhires.com boards. Skip www, info, and support subdomains, plus authenticated *.grad.allhires.com graduate application portals — none serve the public candidate API.
Drop rows where ApplyingForID is missing or <= 0, or where CandidateTitle is blank, before building canonical /app/PositionDetails?id= URLs.
ClosingDate is a UK (Europe/London) date such as 31/12/2026. Parse it as dd/mm/yyyy at end-of-day rather than the mm/dd/yyyy default.
- 1Bootstrap /app/ once per tenant and reuse the token and cookie across all API calls.
- 2Send __RequestVerificationToken as both a request header and a cookie (double-submit).
- 3Skip Positions rows where ApplyingForID <= 0 or CandidateTitle is blank.
- 4Concatenate only non-empty description sections and treat a fully empty body as a parse failure.
- 5Pace requests to ~2 concurrent with ~200ms spacing and back off on HTTP 429.
- 6Restrict scraping to {tenant}.allhires.com; exclude www, info, support, and *.grad hosts.
One endpoint. All AllHires jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=allhires" \
-H "X-Api-Key: YOUR_KEY" Access AllHires
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.