- highCOMPANY_DATA bootstrap missing company_uid or token
- The Careers API call needs both fields from the page's COMPANY_DATA object. Always fetch the canonical /jobs/{tenant}/{board_code} listings URL (strip any trailing job slug first) and abort with a clear error instead of calling the API blind when either field is missing.
- mediumCareers API returns an empty positions array
- An empty [] is authoritative, not a failure — treat zero positions as 'no open roles right now'. Return an empty result and retry later rather than failing the run or falling back to HTML scraping (this provider has no HTML fallback).
- mediumDescription fields appear in two different shapes
- With details=true the Careers API v2 returns details[] at the top level, but older or embedded payloads nest the same array under custom_fields.details. Read the top-level array first and fall back to custom_fields.details so both shapes work.
- mediumRequests blocked or rate-limited (HTTP 403 / 429)
- Comeet returns 403 when a request is blocked and 429 when rate-limited. Keep at least ~200ms between requests, send Accept: application/json plus a Referer matching the board page, and back off exponentially on either status.
- lowSome positions have all-null description fields
- A minority of postings ship custom_fields.details entries whose value is null, yielding an empty description. Skip null/empty values when concatenating and don't assume every listing carries a description.
- lowNot every position includes location metadata
- Some tenants leave the location object null on individual postings. Build location fields defensively with .get() and omit them when absent rather than raising a KeyError.
Comeet Jobs API.
Pull full HTML descriptions, structured locations, and department data from any Comeet-powered careers board through its token-bootstrapped Careers API — no per-job requests needed.
What's in every response.
Data fields, real-world applications, and the companies already running on Comeet.
Data fields
- Full HTML job descriptions
- Structured location data
- Department & team labels
- Employment & experience level
- Workplace type (remote/hybrid)
- Position UID & update timestamps
Use cases
- 01Tech Job Board Aggregation
- 02Startup Hiring Trackers
- 03Talent Market Monitoring
- 04Remote Job Feeds
Trusted by
- monday.com
- Fiverr
- Bright Data
- Plus500
- SeekingAlpha
DIY GUIDE
How to scrape Comeet.
Step-by-step guide to extracting jobs from Comeet-powered career pages—endpoints, authentication, and working code.
Step 1: Fetch the public careers board page
import requests
import re
import json
from urllib.parse import quote
# Canonical listings URL: /jobs/{tenant}/{board_code}
listings_url = "https://www.comeet.com/jobs/monday/41.00B"
response = requests.get(listings_url, timeout=30)
response.raise_for_status()
html = response.text
print(f"Fetched board page: {len(html)} bytes")Step 2: Extract the COMPANY_DATA bootstrap
match = re.search(r"COMPANY_DATA\s*=\s*(\{.*?\});", html, re.DOTALL)
if not match:
raise ValueError("Comeet bootstrap (COMPANY_DATA) not found — check the board URL")
company = json.loads(match.group(1))
company_uid = company.get("company_uid")
token = company.get("token")
company_name = company.get("name")
if not company_uid or not token:
raise ValueError("COMPANY_DATA did not expose company_uid/token")
print(f"Bootstrapped {company_name}: uid={company_uid}")Step 3: Call the Careers API positions endpoint
api_url = (
f"https://www.comeet.co/careers-api/2.0/company/{quote(company_uid)}/positions"
f"?token={quote(token)}&details=true"
)
api_response = requests.get(
api_url,
headers={"Accept": "application/json", "Referer": listings_url},
timeout=30,
)
api_response.raise_for_status()
positions = api_response.json() # JSON array of position objects
print(f"Fetched {len(positions)} positions")Step 4: Map positions to job records
def build_description(position):
# v2 exposes details[] at the top level; older payloads nest it under custom_fields.
details = position.get("details") or (position.get("custom_fields") or {}).get("details") or []
parts = [
d["value"]
for d in sorted(details, key=lambda d: d.get("order") or 0)
if d.get("value") # some entries ship a null value upstream
]
return "\n\n".join(parts)
jobs = []
for p in positions:
uid = p.get("uid")
if not uid:
continue # scraper rejects positions with no uid
loc = p.get("location") or {}
jobs.append({
"id": uid,
"title": (p.get("name") or "").strip(),
"department": p.get("department"),
"location": loc.get("name"),
"city": loc.get("city"),
"state": loc.get("state"),
"country": loc.get("country"),
"is_remote": loc.get("is_remote"),
"employment_type": p.get("employment_type"),
"experience_level": p.get("experience_level"),
"workplace_type": p.get("workplace_type"),
"listing_url": p.get("url_comeet_hosted_page") or p.get("url_active_page") or listings_url,
"apply_url": p.get("url_active_page") or p.get("url_comeet_hosted_page") or listings_url,
"description": build_description(p),
"updated_at": p.get("time_updated"),
})
for job in jobs:
print(f" - {job['title']} ({job['location']})")Step 5: Add error handling, dedup, and pacing
import time
def scrape_comeet(listings_url: str, delay: float = 0.2):
resp = requests.get(listings_url, timeout=30)
if resp.status_code == 404:
raise LookupError("Company not found on Comeet (HTTP 404)")
resp.raise_for_status()
m = re.search(r"COMPANY_DATA\s*=\s*(\{.*?\});", resp.text, re.DOTALL)
if not m:
raise ValueError("Comeet bootstrap did not expose company UID/token")
company = json.loads(m.group(1))
uid, token = company.get("company_uid"), company.get("token")
if not uid or not token:
raise ValueError("Missing company_uid or token in COMPANY_DATA")
time.sleep(delay) # backend paces requests ~200ms apart
api_url = (
f"https://www.comeet.co/careers-api/2.0/company/{quote(uid)}/positions"
f"?token={quote(token)}&details=true"
)
api = requests.get(
api_url,
headers={"Accept": "application/json", "Referer": listings_url},
timeout=30,
)
if api.status_code in (403, 429):
raise RuntimeError(f"Comeet blocked/rate-limited (HTTP {api.status_code}) — back off")
api.raise_for_status()
positions = api.json()
if not positions:
return [] # authoritative empty array — tenant may have paused hiring
seen, out = set(), []
for p in positions:
uid = p.get("uid")
if not uid or uid in seen:
continue
seen.add(uid)
out.append(p)
return out
# Scrape several boards
for url in [
"https://www.comeet.com/jobs/plus500_bulgaria/89.006",
"https://www.comeet.com/jobs/seekingalpha/46.006",
]:
try:
results = scrape_comeet(url)
print(f"{url}: {len(results)} positions")
except Exception as e:
print(f"{url}: {e}") Common issues
Best practices
- 1Fetch the COMPANY_DATA token fresh each run — it is short-lived and tied to the board
- 2Request the positions endpoint with details=true so full descriptions arrive inline, avoiding per-job fetches
- 3Send Accept: application/json and a Referer matching the board page, as the backend does
- 4Order description fields by their `order` value before concatenating, and skip null values
- 5Deduplicate positions on uid and drop entries that have no uid
- 6Space requests ~200ms apart and back off on HTTP 403/429
Or skip the complexity
One endpoint. All Comeet jobs. No scraping, no sessions, no maintenance.
Get API accesscURL
curl "https://connect.jobo.world/api/jobs?sources=comeet" \
-H "X-Api-Key: YOUR_KEY"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.
Ready to integrate Access Comeet
Access Comeet
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.
99.9%API uptime
<200msAvg response
50M+Jobs processed