All platforms

JoblinkApply Jobs API.

Extract structured listings and JSON-LD-backed job detail from any numeric JoblinkApply tenant board — Jobo normalizes titles, locations, employment types, and apply links behind one unified API.

Get API access
JoblinkApply
Live
<3haverage discovery time
1hrefresh interval
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 JoblinkApply.

Data fields
  • Full Job Descriptions
  • Structured Locations
  • Employment Type
  • Posted & Closing Dates
  • Hiring Organization Name
  • Canonical Apply URLs
Use cases
  1. 01Local & Facilities Job Aggregation
  2. 02Employer Board Monitoring
  3. 03JSON-LD Detail Enrichment
  4. 04Apply-Link Syndication
DIY GUIDE

How to scrape JoblinkApply.

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

HTMLintermediateNo published limit; throttle to ~2 req/s (500 ms between requests)No auth

Identify the numeric tenant ID

Every JoblinkApply board lives at /Joblink/{tenantId}, where the tenant ID is numeric. Pull it from the board or job-detail URL — you will need it to build both the listings and detail requests.

Step 1: Identify the numeric tenant ID
import re

# JoblinkApply boards and jobs both live under /Joblink/{tenantId} (numeric).
def tenant_id_from_url(url: str) -> str:
    match = re.search(r"/Joblink/(\d+)", url)
    if not match:
        raise ValueError(f"No numeric JoblinkApply tenant in URL: {url}")
    return match.group(1)

tenant_id = tenant_id_from_url("https://www.joblinkapply.com/Joblink/6924")

Fetch the SearchWithFilters results table

JoblinkApply exposes no JSON/RSS listing feed. The GET-friendly SearchWithFilters route returns the full results table as server-rendered HTML in a single request — no prior POST or saved-search ID required.

Step 2: Fetch the SearchWithFilters results table
import requests

BASE = "https://www.joblinkapply.com"
HEADERS = {"User-Agent": "Mozilla/5.0 (compatible; JoboBot/1.0)"}

def fetch_listings_html(tenant_id: str) -> str:
    url = (
        f"{BASE}/Joblink/{tenant_id}"
        "/Search/SearchWithFilters?WithinDistance=25&SortDirection=Descending"
    )
    resp = requests.get(url, headers=HEADERS, timeout=30)
    resp.raise_for_status()
    return resp.text

Parse job links from the result rows

Each result row holds an anchor to /Joblink/{tenantId}/Job/Index/{jobId}. Keep only rows whose tenant ID matches the board you requested, and dedupe by job ID to drop repeated or cross-tenant links.

Step 3: Parse job links from the result rows
from bs4 import BeautifulSoup

JOB_PATH = re.compile(r"^/Joblink/(\d+)/Job/Index/(\d+)")

def parse_listings(html: str, tenant_id: str) -> list[dict]:
    soup = BeautifulSoup(html, "html.parser")
    jobs, seen = [], set()
    for anchor in soup.select("tr a[href]"):
        match = JOB_PATH.match(anchor.get("href", ""))
        if not match:
            continue
        href_tenant, job_id = match.group(1), match.group(2)
        # Defensive: only accept rows that belong to the requested tenant.
        if href_tenant != tenant_id or job_id in seen:
            continue
        seen.add(job_id)
        jobs.append({
            "job_id": job_id,
            "title": anchor.get_text(strip=True),
            "detail_url": f"{BASE}/Joblink/{tenant_id}/Job/Index/{job_id}",
        })
    return jobs

Read detail from JSON-LD with an HTML fallback

The canonical detail page embeds a JSON-LD JobPosting block. JoblinkApply sometimes leaves unescaped quotes inside the description string, which breaks JSON parsing — so keep the visible Job Summary section as a fallback for the body copy.

Step 4: Read detail from JSON-LD with an HTML fallback
import json

def fetch_job_detail(tenant_id: str, job_id: str) -> dict:
    url = f"{BASE}/Joblink/{tenant_id}/Job/Index/{job_id}"
    resp = requests.get(url, headers=HEADERS, timeout=30)
    resp.raise_for_status()
    soup = BeautifulSoup(resp.text, "html.parser")

    posting = None
    for tag in soup.find_all("script", type="application/ld+json"):
        try:
            data = json.loads(tag.string or "")
        except (json.JSONDecodeError, TypeError):
            continue  # unescaped quotes in description can break the block
        for node in (data if isinstance(data, list) else [data]):
            if isinstance(node, dict) and node.get("@type") == "JobPosting":
                posting = node
                break

    posting = posting or {}
    heading = soup.select_one("h1#JobTitle") or soup.select_one("h1")
    title = posting.get("title") or (heading.get_text(strip=True) if heading else None)

    description = posting.get("description")
    if not description:
        section = (soup.select_one("section[aria-label='Job Summary']")
                   or soup.select_one("#JobSeekerContentArea"))
        description = section.get_text("\n", strip=True) if section else None

    org = posting.get("hiringOrganization") or {}
    return {
        "job_id": job_id,
        "title": title,
        "description": description,
        "company_name": org.get("name") if isinstance(org, dict) else None,
        "employment_type": posting.get("employmentType"),
        "date_posted": posting.get("datePosted"),
        "valid_through": posting.get("validThrough"),
        "apply_url": url,
    }
Common issues
mediumThe JSON-LD JobPosting block fails to parse because the description string contains unescaped HTML-attribute quotes.

Wrap json.loads in a try/except and fall back to the visible section[aria-label='Job Summary'] (or #JobSeekerContentArea) for the description while still reading the other fields from JSON-LD.

lowThe results table can surface anchors for another tenant or repeat the same job across rows.

Only accept rows whose /Joblink/{id}/Job/Index/{jobId} tenant equals the board you requested, and dedupe by numeric job ID.

highBursts of requests return HTTP 403 (blocked) or 429 (rate limited).

Cap concurrency to ~3 detail fetches with roughly 500 ms between requests and back off when you see 403 or 429.

mediumDetail fetches with a non-numeric tenant or job ID are rejected, and a 404 on a valid detail URL means the job was delisted.

Validate that both IDs are digits before requesting, and treat a detail-page 404 as a removed job rather than a transient error to retry.

Best practices
  1. 1Rebuild listing and detail URLs from the numeric tenant and job IDs rather than trusting persisted or anchor hrefs.
  2. 2Prefer JSON-LD JobPosting fields, but always keep the HTML Job Summary fallback for descriptions.
  3. 3Throttle to ~3 concurrent detail fetches with ~500 ms between requests to avoid 403/429.
  4. 4Parse posted and valid-through dates as US (en-US) format and store as UTC — the board exposes no tenant time zone.
  5. 5Dedupe jobs by numeric job ID and drop any row that belongs to another tenant.
  6. 6Send a browser-like User-Agent; boards serve server-rendered HTML and need no API key.
Or skip the complexity

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

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

Access JoblinkApply
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