All platforms

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.

Get API access
AcquireTM
Live
<3haverage discovery time
1hrefresh interval
Companies using AcquireTM
The Conference Board
Developer tools

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.

Data fields
  • Structured Job Titles
  • Full Job Descriptions
  • City & State Locations
  • Posting Dates
  • Numeric Job IDs
  • Direct Apply URLs
Use cases
  1. 01Job Board Aggregation
  2. 02SMB Hiring Trends
  3. 03Recruitment Market Research
  4. 04Careers Page Monitoring
  5. 05ATS Data Pipelines
Trusted by
The Conference Board
DIY GUIDE

How to scrape AcquireTM.

Step-by-step guide to extracting jobs from AcquireTM-powered career pages—endpoints, authentication, and working code.

HybridintermediateNo documented server limit; self-throttle to ~3 concurrent requests, ~300ms apartNo auth

Resolve the tenant from the AcquireTM URL

Every AcquireTM career board lives on a per-company subdomain of acquiretm.com. The leftmost host segment is the tenant slug used to build every API call, so extract and lower-case it first.

Step 1: Resolve the tenant from the AcquireTM URL
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"

Fetch active postings from the CareerCenterServices endpoint

The listings live behind a legacy ASMX web service. GET GetActivePosting, which returns an XML <string> envelope whose text content is the real JSON payload — so parse the XML first, then json.loads the inner string.

Step 2: Fetch active postings from the CareerCenterServices endpoint
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 []

Decode the positional Table rows

Each posting is an untagged array, so fields are read by index: [0] job ID, [1] title, [2] description HTML (falling back to [3]), [4] posted date, [5] state, [6] location. Guard short rows, require a numeric ID, and skip the trailing null sentinel the serializer appends.

Step 3: Decode the positional Table rows
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 jobs

Fetch the full description per posting

The Table description can be truncated. Call GetPostingDesc?Job_ID=<id> for the complete HTML, unwrapping the same XML envelope. Unknown IDs return HTTP 200 with an empty envelope, so treat an empty body as inconclusive rather than a removed job.

Step 4: Fetch the full description per posting
def 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 None
Common issues
highGetActivePosting returns XML, not JSON

The 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.

mediumPostings are positional arrays with no field names

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.

lowA trailing null sentinel row appears in Table

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.

mediumPosted dates use Microsoft JSON date format

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.

mediumGetPostingDesc returns an empty envelope for unknown IDs

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.

mediumAggressive request rates trigger HTTP 403 blocks

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.

Best practices
  1. 1Always unwrap the ASMX XML envelope before parsing the inner JSON or HTML
  2. 2Throttle to roughly 3 concurrent detail requests with ~300ms between calls to avoid 403 blocks
  3. 3Validate that row[0] is numeric and de-duplicate on job ID before persisting
  4. 4Convert /Date(...)/ timestamps to UTC datetimes at ingest
  5. 5Treat an empty GetPostingDesc envelope as inconclusive, never as a removed job
  6. 6Normalize 'CA, San Francisco'-style strings into 'City, State' when parsing locations
Or skip the complexity

One endpoint. All AcquireTM jobs. No scraping, no sessions, no maintenance.

Get API access
cURL
curl "https://connect.jobo.world/api/jobs?sources=acquiretm" \
  -H "X-Api-Key: YOUR_KEY"
Ready to integrate

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.

99.9%API uptime
<200msAvg response
50M+Jobs processed