# JazzHR Jobs API: Export JazzHR Job Listings (XML Feed)

> JazzHR's public XML feed returns every open job for an account, with full descriptions and no API key. Feed URL, fields, dates, and the gotchas.

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

TL;DR: JazzHR publishes a keyless XML export at app.jazz.co/feeds/export/jobs/{company} that lists every open job with title, location, full HTML description, employment type and experience level in one request. The feed has no posting date; that comes from the schema.org JobPosting JSON-LD on each job page at {company}.applytojob.com. There's no salary field in the feed, and some accounts disable the export, in which case you read the career page and its JSON-LD instead.

To get a company's jobs from JazzHR, request `https://app.jazz.co/feeds/export/jobs/{company}`: it returns every open job as XML, with full descriptions, and needs no API key. It isn't JSON, but it's one request per company and converts to JSON in a few lines of code.

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 JazzHR 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://app.jazz.co/feeds/export/jobs/{company}` (XML) |
| API key | None |
| Pagination | None: one response holds every job |
| Full description | Yes, in the feed |
| Salary field | No; occasionally `baseSalary` in the job page's JSON-LD |
| Remote | No flag; infer from title or location text |

## Finding the Company Name

JazzHR career sites live at `https://{company}.applytojob.com/apply`, and the subdomain is the value the feed wants. For a career site at `acme.applytojob.com`, the feed URL is:

```
https://app.jazz.co/feeds/export/jobs/acme
```

Note that the feed lives on JazzHR's shared `app.jazz.co` host, not on the company's `applytojob.com` subdomain. If a company embeds its JazzHR jobs on its own site, look for links to `{company}.applytojob.com` in the page or the Network tab.

## The Feed Request

```bash
curl -H "accept: application/xml" "https://app.jazz.co/feeds/export/jobs/acme"
```

Each open job is a `<job>` element, and every value is wrapped in `CDATA`:

```xml
<jobs>
  <job>
    <id><![CDATA[job_20260720175631_NWOCXGKSJ09O7H5V]]></id>
    <internalcode><![CDATA[972]]></internalcode>
    <status><![CDATA[Open]]></status>
    <title><![CDATA[Front Desk Coordinator]]></title>
    <department><![CDATA[Operations]]></department>
    <url><![CDATA[http://acme.applytojob.com/apply/J0M3IsBoE4/Front-Desk-Coordinator]]></url>
    <city><![CDATA[New York]]></city>
    <state><![CDATA[NY]]></state>
    <country><![CDATA[United States]]></country>
    <postalcode><![CDATA[]]></postalcode>
    <description><![CDATA[<b>Position:</b> Front Desk Coordinator<br><b>Pay Range:</b>&#160;$20.00 hourly USD ...]]></description>
    <type><![CDATA[Full Time]]></type>
    <experience><![CDATA[Entry Level]]></experience>
  </job>
</jobs>
```

## Feed Fields

| Field | What it holds |
|---|---|
| `id` | JazzHR's job ID, e.g. `job_20260720175631_...` |
| `status` | Keep only `Open` |
| `title` | Job title |
| `department` | Department label, if set |
| `url` | Public job page on `applytojob.com` |
| `city`, `state` | City, and usually a state abbreviation |
| `country` | A full country name, such as `United States` |
| `postalcode` | Usually empty |
| `description` | Full HTML description |
| `type` | Employment type, such as `Full Time` |
| `experience` | Experience level, such as `Entry Level` |

The feed also has fields you can ignore, such as `buttons`, which holds an apply-widget script snippet.

## Converting the Feed to JSON (Python)

```python
import json
import re
import requests
import xml.etree.ElementTree as ET

def fetch_jazzhr_jobs(company: str) -> list:
    r = requests.get(
        f"https://app.jazz.co/feeds/export/jobs/{company}",
        headers={"accept": "application/xml"},
        timeout=20,
    )
    r.raise_for_status()
    root = ET.fromstring(r.content)
    fields = ["id", "status", "title", "department", "url", "city",
              "state", "country", "description", "type", "experience"]
    jobs = []
    for job in root.iter("job"):
        row = {f: (job.findtext(f) or "").strip() for f in fields}
        if row["status"] and row["status"].lower() != "open":
            continue
        jobs.append(row)
    return jobs

JSONLD_RE = re.compile(r'<script[^>]+type="application/ld\+json"[^>]*>(.*?)</script>', re.S)

def fetch_date_posted(job_url: str):
    """The feed has no date; the job page's JobPosting JSON-LD does."""
    html = requests.get(job_url.replace("http://", "https://", 1), timeout=20).text
    for block in JSONLD_RE.findall(html):
        try:
            data = json.loads(block)
        except ValueError:
            continue
        if data.get("@type") == "JobPosting":
            return data.get("datePosted")
    return None
```

`ElementTree` unwraps the `CDATA` sections for you, so `description` comes back as an HTML string ready to sanitize and store.

## Getting the Posting Date

The feed has no date field at all. If you need to know when a job was posted, fetch the job's `url` and read the schema.org `JobPosting` block in its JSON-LD. Its `datePosted` is a plain date such as `2026-07-22`, and the block also has `validThrough`, `employmentType` (such as `FULL_TIME`) and `experienceRequirements`. See our [JobPosting schema guide](https://cleanjobdata.com/articles/jobposting-schema-guide) for the field reference.

That costs one page request per job, so only do it for jobs you haven't seen before. Store the date the first time and skip the page for known IDs on later runs.

## When the Feed Is Disabled

The XML export is a per-account setting, and some accounts turn it off even though their career site is live. The feed then answers with a 404 or 403. In that case:

1. Fetch `https://{company}.applytojob.com/apply` and collect the job links, which look like `/apply/{jobId}/{slug}`. The same link often appears twice per job, so dedupe by `jobId`.
2. Fetch each job page and read its `JobPosting` JSON-LD for the title, description, location, date and employment type.

This route costs a request per job, but the JSON-LD has everything the feed has plus the date.

## Gotchas

**IDs differ between the feed and the URL.** The feed's `id` looks like `job_20260720175631_...`, while the job URL contains a short token such as `J0M3IsBoE4`. Pick one as your dedup key and stick with it. The job page's JSON-LD carries the feed-style ID as `uniqueJobCode`, which lets you match the two.

**Country is a full name.** `country` is `United States`, not `US`, so you need a name-to-code lookup. Some jobs have no city or state and only a country, and some use a placeholder such as `Multiple Countries`, which isn't a place. The [location normalization guide](https://cleanjobdata.com/guides/job-location-normalization) covers these cases.

**Descriptions contain HTML entities.** Expect `&#160;` and inline styles inside the `CDATA`. Decode entities and sanitize the HTML before you render it.

**There's no salary field.** Pay, when an employer shares it, usually sits in the description text as something like "Pay Range: $20.00 hourly USD". The job page's JSON-LD can carry a structured `baseSalary`, but it's rare on JazzHR.

**No remote flag.** Look for "remote" or "hybrid" in the title and location text, and accept that some remote jobs only say so in the description.

**Job URLs may use `http://`.** Upgrade them to `https://` before you fetch or display them.

**Edits are easy to miss.** If you only fetch job pages for new IDs, you won't notice when an employer edits a job. The feed itself is cheap, so re-read titles and descriptions from it on every run.

## Public Feed vs JazzHR's Official API

JazzHR also has an official API for its customers. It needs an API key from the employer's own JazzHR account and covers that company's hiring data, such as applicants. You don't need it for published job ads: the export feed above is public and returns what's on the company's career site.

## When This Is Enough

For one company's jobs, the feed is about as easy as it gets: one request, every open job, full descriptions. Add the job-page lookup if you need dates.

## When It Isn't

Across many companies and platforms you end up with a collector per ATS, each with its own format, date source 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 JazzHR alongside other platforms and returns one schema, with locations resolved to cities, states and countries, a remote type, and salary parsed from the description when the employer includes 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 JazzHR 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=coordinator&domain=acme.applytojob.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`. JazzHR jobs apply on each company's own `applytojob.com` subdomain, so this scopes the query to one employer; use `company_website_url` or `employer_id` as alternatives. 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 JazzHR have a public jobs API?

JazzHR has a public XML export rather than a JSON API. `https://app.jazz.co/feeds/export/jobs/{company}` returns every open job for an account with full descriptions, and it needs no API key.

### How do I find a company's JazzHR feed URL?

Take the subdomain from the company's career site at `{company}.applytojob.com` and put it at the end of `https://app.jazz.co/feeds/export/jobs/`. For `acme.applytojob.com`, the feed is `https://app.jazz.co/feeds/export/jobs/acme`.

### Does the JazzHR feed include the posting date?

No. The feed has no date field. Fetch each job's page and read `datePosted` from its schema.org `JobPosting` JSON-LD, and store it the first time you see the job.

### What if the JazzHR feed returns 404?

The account has probably disabled the export. Read the career page at `{company}.applytojob.com/apply` for job links instead, then parse the JSON-LD on each job page.
