AcquireTM Jobs API.
Pull structured postings straight from the CareerCenterServices web service behind AcquireTM career boards, turning titles, locations, dates, and full descriptions into clean job data.
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 AcquireTM.
- Structured Job Titles
- Full Job Descriptions
- City & State Locations
- Posting Dates
- Numeric Job IDs
- Direct Apply URLs
- 01Job Board Aggregation
- 02SMB Hiring Trends
- 03Recruitment Market Research
- 04Careers Page Monitoring
- 05ATS Data Pipelines
How to scrape AcquireTM.
Step-by-step guide to extracting jobs from AcquireTM-powered career pages—endpoints, authentication, and working code.
import re
SUBDOMAIN_RE = re.compile(r"^(?:https?://)?([^.]+)\.acquiretm\.com", re.IGNORECASE)
def extract_tenant(url: str) -> str | None:
match = SUBDOMAIN_RE.match(url)
return match.group(1).lower() if match else None
# "https://conference-board.acquiretm.com/jobs.aspx" -> "conference-board"import json
import requests
from xml.etree import ElementTree
def fetch_active_postings(tenant: str, session: requests.Session) -> list:
url = f"https://{tenant}.acquiretm.com/CareerCenterServices.asmx/GetActivePosting"
resp = session.get(url, timeout=30)
resp.raise_for_status()
# ASMX wraps the JSON payload inside an XML <string> element.
envelope = ElementTree.fromstring(resp.text)
payload = (envelope.text or "").strip()
if not payload:
return []
data = json.loads(payload)
return data.get("Table") or []from datetime import datetime, timezone
MS_DATE_RE = re.compile(r"^/Date\((-?\d+)(?:[-+]\d{4})?\)/$")
def parse_ms_date(value: str | None):
if not value:
return None
match = MS_DATE_RE.match(value)
if match:
return datetime.fromtimestamp(int(match.group(1)) / 1000, tz=timezone.utc)
return None
def decode_rows(rows: list, tenant: str) -> list[dict]:
jobs, seen = [], set()
for row in rows:
# Skip the null sentinel row and any malformed / short rows.
if not isinstance(row, list) or len(row) < 2:
continue
job_id = (row[0] or "").strip()
if not job_id.isdigit() or job_id in seen:
continue
seen.add(job_id)
jobs.append({
"id": job_id,
"title": row[1],
"description_html": row[2] or (row[3] if len(row) > 3 else None),
"posted_at": parse_ms_date(row[4] if len(row) > 4 else None),
"state": row[5] if len(row) > 5 else None,
"location": row[6] if len(row) > 6 else None,
"listing_url": f"https://{tenant}.acquiretm.com/job_details_clean.aspx?ID={job_id}",
})
return jobsdef fetch_description(tenant: str, job_id: str, session: requests.Session) -> str | None:
url = f"https://{tenant}.acquiretm.com/CareerCenterServices.asmx/GetPostingDesc"
resp = session.get(url, params={"Job_ID": job_id}, timeout=30)
resp.raise_for_status()
envelope = ElementTree.fromstring(resp.text)
html = (envelope.text or "").strip()
# 200 + empty envelope means the ID is unknown — inconclusive, not removed.
return html or NoneThe ASMX endpoint wraps the JSON payload inside an XML <string> element. Parse the XML first (ElementTree.fromstring), read the root element's text, then json.loads that inner string. Calling json.loads on the raw response body will fail.
Rows in the Table array are untagged lists read by index, so a short or shifted row silently corrupts fields. Guard on len(row) >= 2, require row[0] to be all-digits, and use row[2] with a row[3] fallback for the description.
The legacy ASMX serializer appends a null entry to the Table array as protocol framing, not a real posting. Skip any row that is not a list before decoding so it is not counted as a job.
Dates arrive as /Date(1699999999000)/ (milliseconds since epoch, with an optional timezone offset) rather than ISO-8601. Regex-extract the millisecond integer and convert from the Unix epoch to a UTC datetime.
The description service responds with HTTP 200 and an empty XML string when a job ID no longer exists, so a missing description is ambiguous. Keep the listing-level data and retry instead of treating an empty body as proof the job was removed.
Unthrottled hits against the shared ASMX service can return HTTP 403. Limit concurrency to a few detail requests at a time and add a short delay between calls to stay under the block threshold.
- 1Always unwrap the ASMX XML envelope before parsing the inner JSON or HTML
- 2Throttle to roughly 3 concurrent detail requests with ~300ms between calls to avoid 403 blocks
- 3Validate that row[0] is numeric and de-duplicate on job ID before persisting
- 4Convert /Date(...)/ timestamps to UTC datetimes at ingest
- 5Treat an empty GetPostingDesc envelope as inconclusive, never as a removed job
- 6Normalize 'CA, San Francisco'-style strings into 'City, State' when parsing locations
One endpoint. All AcquireTM jobs. No scraping, no sessions, no maintenance.
Get API accesscurl "https://connect.jobo.world/api/jobs?sources=acquiretm" \
-H "X-Api-Key: YOUR_KEY" Access AcquireTM
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.