Stream newly discovered career sites — company listing pages and careers portals — in high-volume batches. Each row is a slim card-sized payload with a link to the full company profile.
The Career Sites Feed surfaces career sites — company listing pages, careers portals, and dedicated jobs subdomains — as Jobo discovers them. Each row carries just enough to render a card: the same lightweight company fields you’d see in a job preview, plus the ATS the site runs on, the listing URL where its jobs are posted, and a details_url to fetch the fully enriched company profile on demand.
Stream career sites — POST /api/career-sites/feed. Cursor-paginated stream of slim career-site rows.
There is no companion “expired” endpoint: unlike job postings, career sites don’t expire — they get updated. Use updated_after to pick up changes on subsequent runs. When you need the rich company data (funding, leadership, ratings, etc.), follow details_url (or call GET /api/companies/{id}) for the rows you care about.
Pricing is 50 credits ($0.05) per row returned. Page cap is 50 rows per request.
A typical integration runs an initial backfill, then an incremental sync on a schedule. Use the company id as your primary key — it’s stable across updates, so syncs are simple upserts.
Page through the entire feed once with cursor pagination. Persist next_cursor after every successful batch so a crash resumes mid-stream rather than restarting from page 1. Stop when has_more is false.
This sample implements the full workflow against a small key-value store. Replace store with your real database (Postgres upsert, etc.).
"""Incremental sync: pages newly discovered / updated career sites since the last run. Cursor is checkpointed so a crash resumes mid-stream."""import json, time, datetime as dt, requests, pathlibAPI_KEY = "YOUR_API_KEY"URL = "https://connect.jobo.world/api/career-sites/feed"STATE = pathlib.Path("career_sites_sync_state.json")OVERLAP = dt.timedelta(minutes=15)state = json.loads(STATE.read_text()) if STATE.exists() else {"last_run_at": None, "cursor": None}this_run_at = dt.datetime.now(dt.timezone.utc).isoformat()store = {} # ← your DB. key = company["id"]def post(body): r = requests.post(URL, headers={"X-Api-Key": API_KEY, "Content-Type": "application/json"}, json=body) r.raise_for_status() return r.json()updated_after = Noneif state["last_run_at"]: updated_after = (dt.datetime.fromisoformat(state["last_run_at"]) - OVERLAP).isoformat()cursor = state.get("cursor")while True: body = {"batch_size": 50} if updated_after: body["updated_after"] = updated_after if cursor: body["cursor"] = cursor data = post(body) for site in data["career_sites"]: store[site["id"]] = site # ← upsert in your DB cursor = data["next_cursor"] STATE.write_text(json.dumps({**state, "cursor": cursor})) # checkpoint if not data["has_more"]: break# Commit the new checkpoint only after the sync succeeded.STATE.write_text(json.dumps({"last_run_at": this_run_at, "cursor": None}))print(f"Synced. {len(store)} career sites in store.")
ATS / job-board provider the career site runs on (e.g. "greenhouse", "lever", "workday").
listing_url
string
Career-site URL where the company’s jobs are listed.
discovered_at
string|null
UTC timestamp the career site was first discovered. Stable across re-enrichment runs.
updated_at
string
UTC timestamp the company profile was last updated.
details_url
string|null
Authenticated URL to the fully enriched company profile (GET /api/companies/{id}). Same X-Api-Key as the rest of the API. Returns funding, leadership, ratings, press, etc.