All platforms

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.

Get API access
Comeet
Live
30K+jobs indexed monthly
<3haverage discovery time
1hrefresh interval
Companies using Comeet
monday.comFiverrBright DataPlus500SeekingAlpha
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 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
  1. 01Tech Job Board Aggregation
  2. 02Startup Hiring Trackers
  3. 03Talent Market Monitoring
  4. 04Remote Job Feeds
Trusted by
monday.comFiverrBright DataPlus500SeekingAlpha
DIY GUIDE

How to scrape Comeet.

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

HybridintermediateNo published limit; keep ~200ms between requests and back off on HTTP 403/429No auth

Fetch the public careers board page

Every Comeet board lives at /jobs/{tenant}/{board_code}. Fetch that page first — it embeds the bootstrap object you need to reach the structured Careers API. Strip any trailing job slug to get the canonical listings URL.

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")

Extract the COMPANY_DATA bootstrap

The page defines a COMPANY_DATA JavaScript object holding the company UID and a short-lived token. Both are required to authenticate the Careers API call in the next step, so abort clearly if either is missing.

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}")

Call the Careers API positions endpoint

Hit the structured Careers API (note the .co domain). Passing details=true returns full description fields inline, so a single call yields every job with its complete content — no per-job requests. Send Accept and a Referer matching the board page.

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")

Map positions to job records

Each position returns snake_case fields. Build the description by concatenating detail values in `order`; the fields live at the top level with details=true, but older payloads nest them under custom_fields.details, so read both. Prefer url_comeet_hosted_page for the listing URL and url_active_page for applying.

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']})")

Add error handling, dedup, and pacing

Wrap the flow for production: classify HTTP errors (404 = unknown company, 403/429 = blocked/rate-limited), treat an empty array as an authoritative 'no open roles', deduplicate on uid, and space requests out.

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

Best practices
  1. 1Fetch the COMPANY_DATA token fresh each run — it is short-lived and tied to the board
  2. 2Request the positions endpoint with details=true so full descriptions arrive inline, avoiding per-job fetches
  3. 3Send Accept: application/json and a Referer matching the board page, as the backend does
  4. 4Order description fields by their `order` value before concatenating, and skip null values
  5. 5Deduplicate positions on uid and drop entries that have no uid
  6. 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 access
cURL
curl "https://connect.jobo.world/api/jobs?sources=comeet" \
  -H "X-Api-Key: YOUR_KEY"
Ready to integrate

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