- mediumRequesting page_size above 20 still returns only 20 rows
- The server hard-caps page_size at 20. Do not assume a bigger page; instead loop pages and stop when the response 'next' field is null.
- highCompany slug returns HTTP 404
- The slug in the careers-page.com URL is the tenant id. Confirm it against the /api/v1.0/c/{slug}/ endpoint, and note both www.careers-page.com and careers-page.com are valid hosts for the same board.
- lowSalary is missing or the currency looks wrong
- salary_min/salary_max are null when is_salary_visible is false. Trust currency_code from the listings API (ISO); the HTML detail page renders currency as a human-readable name like 'US Dollar' that you must map back to an ISO code.
- lowSome postings have no location fields
- Not every tenant populates city/state/country or location_display. Fall back gracefully and treat location as optional rather than assuming every job has it.
- lowDescriptions arrive as raw HTML
- The description field is unsanitized HTML. Strip or sanitize it before rendering or indexing to avoid broken markup and injection risks.
- mediumBursts of requests get 403 or 429 responses
- Manatal blocks aggressive scraping. Add a ~250ms delay between requests, cap concurrent detail fetches (about 5), and back off on 429 before retrying.
Manatal Jobs API.
Pull structured job listings straight from Manatal's careers-page.com boards via a clean, paginated JSON API — full HTML descriptions, salary ranges, and locations, no authentication required.
What's in every response.
Data fields, real-world applications, and the companies already running on Manatal.
Data fields
- Full HTML Job Descriptions
- Structured Salary Ranges
- City, State & Country Locations
- Posted & Closing Dates
- Employment Type
- Direct Apply URLs
Use cases
- 01Recruitment Agency Monitoring
- 02Job Board Aggregation
- 03Salary Benchmarking
- 04New Role Alerts
How to scrape Manatal.
Step-by-step guide to extracting jobs from Manatal-powered career pages—endpoints, authentication, and working code.
import requests
company_slug = "instaswim-llc"
url = f"https://www.careers-page.com/api/v1.0/c/{company_slug}/"
resp = requests.get(url, timeout=10)
resp.raise_for_status() # 404 here means the company slug is wrong
company = resp.json()
print(company.get("name"), company.get("website"))import requests
def fetch_manatal_jobs(company_slug: str) -> list[dict]:
jobs = []
page = 1
while True:
url = f"https://www.careers-page.com/api/v1.0/c/{company_slug}/jobs/"
params = {
"page": page,
"page_size": 20, # server hard-caps page_size at 20
"ordering": "-is_pinned_in_career_page,-last_published_at",
}
resp = requests.get(url, params=params, timeout=10)
resp.raise_for_status()
data = resp.json()
jobs.extend(data.get("results", []))
if not data.get("next"): # paginate until 'next' is null
break
page += 1
return jobs
all_jobs = fetch_manatal_jobs("instaswim-llc")
print(f"Found {len(all_jobs)} jobs")def parse_job(company_slug: str, job: dict) -> dict:
job_hash = job["hash"]
return {
"external_id": job_hash,
"title": (job.get("position_name") or "").strip(),
"description_html": job.get("description") or "",
"city": job.get("city"),
"state": job.get("state"),
"country": job.get("country"),
"location": job.get("location_display"),
"salary_min": job.get("salary_min"),
"salary_max": job.get("salary_max"),
"currency": job.get("currency_code"), # ISO code in the API
"salary_visible": job.get("is_salary_visible"),
"listing_url": f"https://www.careers-page.com/{company_slug}/job/{job_hash}",
"apply_url": f"https://www.careers-page.com/{company_slug}/job/{job_hash}/apply",
}
jobs = [parse_job("instaswim-llc", j) for j in all_jobs if j.get("hash")]import json
import re
import requests
JSON_LD_RE = re.compile(
r'<script[^>]*type="application/ld\+json"[^>]*>(.*?)</script>',
re.IGNORECASE | re.DOTALL,
)
def fetch_detail(listing_url: str) -> dict:
html = requests.get(listing_url, timeout=10).text
match = JSON_LD_RE.search(html)
if not match:
return {}
try:
ld = json.loads(match.group(1))
except json.JSONDecodeError:
return {} # malformed JSON-LD is non-fatal
return {
"posted_at": ld.get("datePosted"),
"closes_at": ld.get("validThrough"),
"employment_type": ld.get("employmentType"),
}
print(fetch_detail(jobs[0]["listing_url"]))import time
import requests
def safe_get(url, **kwargs):
resp = requests.get(url, timeout=10, **kwargs)
if resp.status_code == 404:
raise LookupError("Company or job not found")
if resp.status_code in (401, 403):
raise PermissionError("Blocked or authentication required")
if resp.status_code == 429:
time.sleep(5)
return safe_get(url, **kwargs)
resp.raise_for_status()
time.sleep(0.25) # ~250ms between requests, max ~5 concurrent details
return resp- 1Paginate by following the 'next' field; never assume page_size can exceed 20
- 2Keep the ordering=-is_pinned_in_career_page,-last_published_at param for stable paging
- 3Prefer currency_code from the listings API (ISO) over the detail page's human-readable currency name
- 4Only fetch the HTML detail page when you need datePosted, validThrough or employmentType
- 5Space requests ~250ms apart and cap detail concurrency to avoid 403/429 blocks
- 6Sanitize the raw HTML description before storing or displaying it
One endpoint. All Manatal jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=manatal" \
-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 Manatal
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.