- highRequests get throttled or blocked (429 / 403)
- SmartRecruiters publishes no official rate limit, so start conservative — roughly 100ms between requests with only a handful of concurrent detail fetches — and apply exponential backoff on any 429 or 403 response, both of which signal throttling.
- mediumCannot find company identifier
- Check the career-page URL structure. If a company embeds SmartRecruiters behind a custom domain, inspect network requests in browser DevTools to find the api.smartrecruiters.com call containing the identifier. Some companies use a name that differs from their brand (e.g. 'TheNielsenCompany' instead of 'Nielsen').
- highTruncated or missing job descriptions
- The listings endpoint returns summary data only. Always call /postings/{jobId} for the full jobAd.sections content (jobDescription, qualifications, companyDescription). A small number of postings omit descriptions upstream even from the details endpoint — keep the record with its other fields rather than dropping it.
- lowMissing location or salary data
- These fields are optional in SmartRecruiters. Check the compensation object for min/max/currency and the customField array for salary or location details companies expose there. Not every employer publishes compensation.
- lowAPI returns empty content for a valid company
- A company with no active postings answers 200 OK with an empty content array and totalFound: 0. Validate that totalFound is greater than zero before starting a full crawl.
- mediumJob ID format varies
- Job IDs are long numeric strings but some integrations return them as numbers. Always treat them as strings to avoid precision loss on large IDs.
SmartRecruiters Jobs API.
Pull structured job postings from a public, no-auth JSON API — complete with department, compensation ranges, and hybrid-work details for thousands of global employers.
What's in every response.
Data fields, real-world applications, and the companies already running on SmartRecruiters.
Data fields
- Full Job Descriptions
- Compensation Ranges
- Department & Function
- Hybrid & Remote Flags
- Experience & Employment Type
- Custom Metadata Fields
Use cases
- 01Global Job Aggregation
- 02Salary Benchmarking
- 03Competitive Hiring Intelligence
- 04Talent Market Analysis
Trusted by
- Visa
- Bosch
- McDonald's
- ServiceNow
- Etsy
- CERN
- OECD
How to scrape SmartRecruiters.
Step-by-step guide to extracting jobs from SmartRecruiters-powered career pages—endpoints, authentication, and working code.
import re
def extract_company_id(url: str) -> str:
"""Extract company identifier from SmartRecruiters URL."""
patterns = [
r'jobs\.smartrecruiters\.com/([^/?]+)',
r'careers\.smartrecruiters\.com/([^/?]+)',
r'api\.smartrecruiters\.com/v1/companies/([^/?]+)',
]
for pattern in patterns:
match = re.search(pattern, url)
if match:
return match.group(1)
raise ValueError(f'Could not extract company ID from {url}')
# Example usage
url = "https://jobs.smartrecruiters.com/Visa"
company_id = extract_company_id(url)
print(f"Company ID: {company_id}") # Output: Visaimport requests
import time
def fetch_all_jobs(company_id: str, page_size: int = 100) -> list[dict]:
"""Fetch all job listings for a company with pagination."""
all_jobs = []
offset = 0
base_url = f"https://api.smartrecruiters.com/v1/companies/{company_id}/postings"
while True:
params = {"limit": page_size, "offset": offset}
response = requests.get(base_url, params=params, timeout=30)
response.raise_for_status()
data = response.json()
jobs = data.get("content", [])
all_jobs.extend(jobs)
total_found = data.get("totalFound", 0)
print(f"Fetched {len(jobs)} jobs (total: {total_found}, offset: {offset})")
# Check if we've fetched all jobs
if len(all_jobs) >= total_found or len(jobs) < page_size:
break
offset += page_size
time.sleep(0.1) # Be respectful with rate limiting
return all_jobs
# Example usage
jobs = fetch_all_jobs("AFCA")
print(f"Total jobs found: {len(jobs)}")import requests
def fetch_job_details(company_id: str, job_id: str) -> dict:
"""Fetch full details for a specific job posting."""
url = f"https://api.smartrecruiters.com/v1/companies/{company_id}/postings/{job_id}"
response = requests.get(url, timeout=30)
response.raise_for_status()
job = response.json()
return {
"id": job.get("id"),
"title": job.get("name"),
"posting_url": job.get("postingUrl"),
"apply_url": job.get("applyUrl"),
"description": job.get("jobAd", {}).get("sections", {}).get("jobDescription", {}).get("text", ""),
"qualifications": job.get("jobAd", {}).get("sections", {}).get("qualifications", {}).get("text", ""),
"company_description": job.get("jobAd", {}).get("sections", {}).get("companyDescription", {}).get("text", ""),
"location": job.get("location", {}).get("fullLocation"),
"city": job.get("location", {}).get("city"),
"country": job.get("location", {}).get("country"),
"remote": job.get("location", {}).get("remote", False),
"hybrid": job.get("location", {}).get("hybrid", False),
"department": job.get("department", {}).get("label"),
"employment_type": job.get("typeOfEmployment", {}).get("label"),
"experience_level": job.get("experienceLevel", {}).get("label"),
"compensation": job.get("compensation"),
"posted_date": job.get("releasedDate"),
"custom_fields": job.get("customField", []),
}
# Example usage
job = fetch_job_details("AFCA", "744000107175576")
print(f"Job: {job['title']}")
print(f"Location: {job['location']}")import requests
def validate_company(company_id: str) -> dict:
"""Validate a company identifier and check for active jobs."""
url = f"https://api.smartrecruiters.com/v1/companies/{company_id}/postings"
params = {"limit": 1, "offset": 0}
try:
response = requests.get(url, params=params, timeout=30)
response.raise_for_status()
data = response.json()
total_found = data.get("totalFound", 0)
if total_found == 0:
return {"valid": False, "reason": "No jobs found or invalid company"}
# Get company name from first posting
company_name = None
if data.get("content"):
company_name = data["content"][0].get("company", {}).get("name")
return {
"valid": True,
"total_jobs": total_found,
"company_name": company_name,
}
except requests.RequestException as e:
return {"valid": False, "reason": str(e)}
# Example usage
result = validate_company("Visa")
print(result) # {'valid': True, 'total_jobs': 150, 'company_name': 'Visa'}
result = validate_company("InvalidCompany123")
print(result) # {'valid': False, 'reason': 'No jobs found or invalid company'}import requests
import time
from typing import Optional
def fetch_with_retry(
url: str,
params: dict = None,
max_retries: int = 3,
delay: float = 0.2
) -> Optional[dict]:
"""Fetch URL with retry logic and rate limiting."""
for attempt in range(max_retries):
try:
time.sleep(delay) # Rate limiting
response = requests.get(url, params=params, timeout=30)
if response.status_code == 404:
print(f"Resource not found: {url}")
return None
if response.status_code == 429:
# Rate limited - wait longer
wait_time = 2 ** attempt
print(f"Rate limited, waiting {wait_time}s...")
time.sleep(wait_time)
continue
response.raise_for_status()
return response.json()
except requests.RequestException as e:
print(f"Attempt {attempt + 1} failed: {e}")
if attempt == max_retries - 1:
raise
return None
# Example usage with caching via ETag
cached_etags = {}
def fetch_with_cache(url: str) -> dict:
"""Fetch with ETag-based caching."""
headers = {}
if url in cached_etags:
headers["If-None-Match"] = cached_etags[url]
response = requests.get(url, headers=headers, timeout=30)
if response.status_code == 304:
print("Using cached response")
return None # Return cached data
if response.ok:
if "ETag" in response.headers:
cached_etags[url] = response.headers["ETag"]
return response.json()
response.raise_for_status()- 1Use limit=100 for optimal pagination throughput
- 2Fetch job details separately — listings never include descriptions
- 3Cache with ETag headers to make re-scrapes cheap
- 4Validate company identifiers before launching a full crawl
- 5Read compensation and the customField array for salary ranges
- 6Capture hybrid work from location.hybrid and hybridDescription
One endpoint. All SmartRecruiters jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=smartrecruiters" \
-H "X-Api-Key: YOUR_KEY"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.
Access SmartRecruiters
job data today.
One API call. Structured data. No scraping infrastructure to build or maintain — start with the $5 free starting balance and scale as you grow.