# Comeet Jobs API: Get Comeet Job Listings as JSON

> Every Comeet career page embeds all open positions as JSON, with full descriptions. How to find a company's page, extract the data, and the field gotchas.

Source: https://cleanjobdata.com/articles/how-to-scrape-comeet-job-listings
Published: 2026-11-01

TL;DR: Comeet (now Spark Hire Recruit) career pages at comeet.com/jobs/{slug}/{companyId} embed every open position as a JSON array assigned to COMPANY_POSITIONS_DATA in the page HTML, so one request with no API key returns all jobs with full descriptions. Both the slug and the company ID are required. Descriptions are split into named sections, the only date is time_updated, the country is an ISO code, and the state field isn't reliable.

To get a company's jobs from Comeet, fetch its career page at `https://www.comeet.com/jobs/{slug}/{companyId}` and parse the `COMPANY_POSITIONS_DATA` JSON array embedded in the HTML. That one request returns every open position with its full description, and no API key is needed.

That's the DIY route, and it works for a handful of companies you already know. If you'd rather not build and maintain a Comeet scraper, CleanJobData already ingests ATS platforms and company career sites and serves the jobs normalized through one REST API — see the [Jobs API docs](https://cleanjobdata.com/docs/api/jobs).

## At a Glance

| | |
|---|---|
| Endpoint | `GET https://www.comeet.com/jobs/{slug}/{companyId}` (JSON embedded in HTML) |
| API key | None |
| Pagination | None: the page holds every open position |
| Full description | Yes, in `custom_fields.details` |
| Salary field | No |
| Remote | `workplace_type` (`Remote`, `Hybrid`, `On-site`) |

Comeet has been rebranded as Spark Hire Recruit, but career pages still live on `comeet.com`.

## Finding the Slug and Company ID

A Comeet career page URL has two parts after `/jobs/`: a slug and a company ID, as in `https://www.comeet.com/jobs/acme/12.345`. Job pages add a title slug and a position ID: `https://www.comeet.com/jobs/acme/12.345/senior-engineer/AB.123`. Both the slug and the company ID are required, and you can't derive one from the other:

- `comeet.com/jobs/acme` without the company ID returns a 404.
- A real slug with the wrong company ID returns HTTP 200, but with a small generic page instead of the company's data.

Copy both values from a real job or careers link. If the company embeds its Comeet jobs on its own site, look for `comeet.com/jobs/` links in the page or the Network tab.

## The Request

```bash
curl "https://www.comeet.com/jobs/acme/12.345"
```

The response is an HTML page. Its inline script declares a few variables and then assigns them, and one of them holds every open position:

```html
<script type="text/javascript">
  var COMPANY_DATA;
  var COMPANY_POSITIONS_DATA;
  var POSITION_DATA;
  COMPANY_DATA = {"name": "Acme", ...};
  COMPANY_POSITIONS_DATA = [ {...}, {...} ];
  POSITION_DATA = null;
</script>
```

Note that the variables are declared first and assigned later without `var`. A pattern that looks for `var COMPANY_POSITIONS_DATA =` finds nothing on most pages; match the bare assignment instead.

One position looks like this (trimmed):

```json
{
  "uid": "7D.766",
  "name": "Senior Backend Engineer",
  "employment_type": "Full-time",
  "experience_level": "Senior",
  "workplace_type": "Hybrid",
  "time_updated": "2026-09-23T13:18:21Z",
  "location": {
    "name": "Austin, TX",
    "city": "Austin",
    "state": "TX",
    "country": "US",
    "is_remote": false
  },
  "custom_fields": {
    "details": [
      { "name": "Description", "value": "<p>We're looking for...</p>", "order": 0 },
      { "name": "Requirements", "value": "<ul><li>5+ years...</li></ul>", "order": 1 }
    ]
  },
  "url_comeet_hosted_page": "https://www.comeet.com/jobs/acme/12.345/senior-backend-engineer/7D.766",
  "url_active_page": "https://www.comeet.com/jobs/acme/12.345/senior-backend-engineer/7D.766"
}
```

## Position Fields

| Field | What it holds |
|---|---|
| `uid` | Position ID; multi-location jobs use `{base}-{suffix}` (see below) |
| `name` | Job title |
| `employment_type` | `Full-time`, `Part-time`, `Hourly` or `None` |
| `experience_level` | `Entry-level`, `Intermediate`, `Senior`, `Management` or `None` |
| `workplace_type` | `Remote`, `Hybrid` or `On-site` |
| `time_updated` | ISO timestamp of the last update |
| `location` | `name`, `city`, `state`, `country` (ISO alpha-2) and `is_remote` |
| `custom_fields.details` | Ordered description sections, each with `name`, `value` (HTML) and `order` |
| `url_comeet_hosted_page` | The job page on `comeet.com` |
| `url_active_page` | A second public URL for the job; use it when the hosted-page URL is missing |

`None` in `employment_type` or `experience_level` means "not set", not a real value. It's common.

## Fetching Jobs (Python)

```python
import json
import re
import requests

POSITIONS_RE = re.compile(r"\bCOMPANY_POSITIONS_DATA\s*=\s*")

def fetch_comeet_jobs(slug: str, company_id: str) -> list:
    r = requests.get(f"https://www.comeet.com/jobs/{slug}/{company_id}", timeout=30)
    r.raise_for_status()
    html = r.text
    if not re.search(r"\bCOMPANY_DATA\s*=", html):
        raise ValueError("No company data: check the slug and company ID")

    match = POSITIONS_RE.search(html)  # skips the bare "var ...;" declaration
    if not match:
        raise ValueError("COMPANY_POSITIONS_DATA not found: the page layout may have changed")
    positions, _ = json.JSONDecoder().raw_decode(html, match.end())

    # Group multi-location rows ("BASE-SUFFIX") into one job per base uid
    groups = {}
    for p in positions:
        groups.setdefault(p["uid"].split("-")[0], []).append(p)

    jobs = []
    for base, rows in groups.items():
        primary = next((p for p in rows if p["uid"] == base), rows[0])
        sections = sorted(primary.get("custom_fields", {}).get("details") or [], key=lambda d: d.get("order", 0))
        html_desc = "".join(
            (f"<h3>{s['name']}</h3>" if s.get("name") else "") + s["value"]
            for s in sections if (s.get("value") or "").strip()
        )
        locations = []
        for p in rows:
            loc = p.get("location") or {}
            place = loc.get("city") or loc.get("name")
            if place and place.lower() != "remote":
                locations.append({"place": place, "country": loc.get("country") or None})
        jobs.append({
            "id": base,
            "title": primary.get("name"),
            "updated": primary.get("time_updated"),
            "workplace_type": primary.get("workplace_type"),
            "employment_type": primary.get("employment_type"),
            "experience_level": primary.get("experience_level"),
            "locations": locations,
            "url": primary.get("url_comeet_hosted_page") or primary.get("url_active_page"),
            "html": html_desc,
        })
    return jobs
```

`raw_decode` reads exactly one JSON value starting at the assignment and ignores the rest of the script, which is safer than a regex that tries to find the closing bracket.

## Gotchas

**Multi-location jobs appear as several positions.** A job posted to several locations shows up as rows that share a base ID with different suffixes, for example `7D.766`, `7D.766-80.50D` and `7D.766-4B.10E`. The rows match except for `location`, and the base row often has no city. Group by the part before the first hyphen and merge the locations, or you'll store one job several times. [Deduplicating job listings](https://cleanjobdata.com/articles/deduplicating-job-listings-across-sources) covers the general problem.

**Don't trust `location.state`.** We've seen postings for Austin, Texas carry `state: "AZ"`. Use `city` and `country`, and resolve the state yourself if you need it. `country` is a clean ISO alpha-2 code.

**Structured location can be empty.** Some on-site jobs have `city`, `state` and `country` all blank while `location.name` still holds the place, such as "Memphis, TN". Fall back to `name` and parse it. See the [location normalization guide](https://cleanjobdata.com/guides/job-location-normalization).

**The only date is `time_updated`.** There's no original posting date. `time_updated` changes when the employer edits the job, so "updated recently" doesn't always mean "posted recently."

**A wrong ID doesn't return an error.** A nonexistent company, or a real slug with the wrong company ID, returns HTTP 200 with a generic marketing page. Check that the page contains a `COMPANY_DATA` assignment before you trust an empty result.

**Description sections vary by company.** Section names and counts are set per employer, commonly two to four sections such as "Description", "Requirements" and a benefits section. Join every section in `order` rather than looking for fixed names. The values are real HTML, not entity-encoded.

**Experience level needs mapping.** `Intermediate` and `Management` don't line up with every seniority scale. The [experience level mapping guide](https://cleanjobdata.com/guides/experience-level-mapping) shows one way to map them.

**There's no salary field.** If pay is disclosed, it's in the description text.

## Embedded Data vs Comeet's Official API

Comeet also has a documented careers API for its customers. It needs credentials that the employer generates in its own Comeet account, so you can only use it for companies that give you access. For published jobs from other companies, the data embedded in the public career page is the route that works.

## When This Is Enough

For one company, a single page fetch gets every open job with full descriptions. It's one of the cheapest platforms to collect from.

## When It Isn't

Across many companies and platforms you end up maintaining a different collector for each, with different identifiers, date fields and location quirks. The [ATS public job postings API list](https://cleanjobdata.com/articles/ats-public-job-postings-api-list) shows how they compare.

CleanJobData reads Comeet career pages alongside other platforms and returns one schema, with locations resolved to cities, states and countries, a remote type, an employment type and an experience level. Comeet has no salary field, so salary is only filled when the description states it. Try it in the [Playground](https://cleanjobdata.com/playground) or see the [API Documentation](https://cleanjobdata.com/docs).

## Skip the scraper: get this data from the CleanJobData API

If you'd rather not maintain a Comeet collector, you can query normalized jobs from ATS platforms and company career sites with a single request. The response has structured city/state/country locations, parsed salary when the employer publishes one, a remote type, and company metadata, and you page through results with the cursor in `pagination.next_page`.

```bash
curl "https://api.cleanjobdata.com/jobs?title=engineer&domain=www.comeet.com&limit=20" \
  -H "Authorization: Bearer $CLEANJOBDATA_API_KEY"
```

The `domain` filter is an exact match on the host of each job's `application_url`, so it only returns jobs whose apply link resolves to `www.comeet.com`; use `company_website_url` or `employer_id` to scope to one employer instead. Coverage depends on which employers are in the dataset, so check that the companies you care about appear before you rely on it.

- [Jobs API reference](https://cleanjobdata.com/docs/api/jobs) for every filter and response field
- [Playground](https://cleanjobdata.com/playground) to run queries without writing code
- [Pricing](https://cleanjobdata.com/pricing) — the Trial is free with no card required

## FAQ

### Does Comeet have a public jobs API?

Not a public JSON endpoint, but every Comeet career page at `comeet.com/jobs/{slug}/{companyId}` embeds all open positions as a JSON array named `COMPANY_POSITIONS_DATA`. One request with no API key returns every job with its full description.

### How do I find a company's Comeet company ID?

It's the second path segment after `/jobs/` in any of the company's Comeet URLs, such as `12.345` in `comeet.com/jobs/acme/12.345`. You need both the slug and the company ID; neither works alone.

### Why does my Comeet request return no jobs?

Check the company ID. A wrong ID returns HTTP 200 with a generic page that has no `COMPANY_DATA` or positions, so it looks like an empty board rather than an error.

### Does Comeet include a posting date or salary?

There's no salary field, and the only date is `time_updated`, the last time the position was edited. Pay, when it's disclosed, appears in the description text.
