> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zenrows.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> The Zenrows API (Fetch, Extract via extract=auto, and Batch) is described in OpenAPI 3.1 at https://www.zenrows.com/openapi.json and listed in the RFC 9727 API catalog at https://www.zenrows.com/.well-known/api-catalog. Use the spec for exact parameter names and types.

# Jobs: search, careers pages and postings

> Get job listings from Indeed and company careers pages as structured data with Zenrows, and pull every job's details at scale.

Job data powers labor-market research, recruiting tools and salary benchmarks. This page covers three jobs: searching a job board, listing every opening a company publishes, and getting each posting's details in bulk. All of it uses public job pages.

**Uses:** [Fetch](/fetch/api-reference) with [Adaptive Stealth Mode](/fetch/features/adaptive-stealth-mode) (`mode=auto`), [Extract](/extract/introduction) (`extract=auto`), [Crawl](/crawl/introduction), [Batch](/batch/introduction), [`css_extractor`](/fetch/features/css-extractor)

## Ask your AI agent

With the [Zenrows MCP server](/mcp/overview) connected to Claude, Cursor or any MCP client, paste this prompt. Your agent picks the right Zenrows tools and settings on its own.

```text theme={"dark"}
Use Zenrows to get every job on https://www.indeed.com/jobs?q=data+engineer&l=Austin%2C+TX
as a visitor from the US. Give me a table of title, company, location and pay, sorted by highest pay.
```

## Search Indeed for jobs

Send an Indeed search URL with `extract=auto`. `parsed.job_listings` holds one entry per job card, with its title, company, location, pay and job key. `extract_fields` keeps only the fields you read.

<CodeGroup>
  ```python Python (Requests) theme={"dark"}
  import requests

  params = {
      "apikey": "YOUR_ZENROWS_API_KEY",
      "url": "https://www.indeed.com/jobs?q=data+engineer&l=Austin%2C+TX",
      "mode": "auto",
      "proxy_country": "us",
      "extract": "auto",
      "extract_fields": "job_listings.title,job_listings.company_name,job_listings.location,job_listings.benefits,job_listings.job_key",
  }

  response = requests.get("https://api.zenrows.com/v1/", params=params, timeout=180)
  response.raise_for_status()
  listings = response.json()["parsed"]["job_listings"]

  print(len(listings), "jobs")
  for job in listings[:3]:
      print(job)
  ```

  ```bash cURL theme={"dark"}
  curl "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fwww.indeed.com%2Fjobs%3Fq%3Ddata%2Bengineer%26l%3DAustin%252C%2BTX&mode=auto&proxy_country=us&extract=auto&extract_fields=job_listings.title%2Cjob_listings.company_name%2Cjob_listings.location%2Cjob_listings.benefits%2Cjob_listings.job_key"
  ```
</CodeGroup>

## Example output

```text theme={"dark"}
17 jobs
{'benefits': ['$224,000 - $279,000 a year', 'Full-time'], 'company_name': 'FluidStack', 'job_key': '188476d650f66c02', 'location': 'Austin, TX', 'title': 'Data Engineer'}
{'benefits': ['$48.08 - $61.06 an hour'], 'company_name': 'Charles Schwab', 'job_key': '0faf084eb66178a8', 'location': 'Hybrid work in Austin, TX', 'title': 'Data Engineer'}
{'benefits': ['Full-time'], 'company_name': 'Aristocrat', 'job_key': '199478155925e9d0', 'location': 'Austin, TX 78757', 'title': 'Data Engineer'}
```

Pay and job type arrive together in `benefits`, as Indeed shows them on the card. Omit `extract_fields` to also get each card's `snippet`, `detail_url` and `company_logo_url`, plus `search_context` with the query and location.

## List every opening on a company careers page

Many companies publish openings on a hosted job board. Point [Crawl](/crawl/setup) at the board and keep only job URLs with `include_patterns`. This example collects GitLab's openings from its Greenhouse board:

```python Python (Requests) theme={"dark"}
import time
import requests

API = "https://api.zenrows.com/v1/crawls"
headers = {"X-API-Key": "YOUR_ZENROWS_API_KEY"}

crawl = requests.post(API, headers=headers, json={
    "url": "https://job-boards.greenhouse.io/gitlab",
    "depth": 1,
    "include_patterns": ["/gitlab/jobs/"],
    "max_items": 500,
}, timeout=60).json()
crawl_id = crawl["crawl_id"]

job_urls, cursor = [], None
while True:
    params = {"cursor": cursor} if cursor else {}
    body = requests.get(f"{API}/{crawl_id}", headers=headers, params=params, timeout=60).json()
    job_urls += [result["url"] for result in body["results"]]
    if body["next_cursor"] is None:
        break
    cursor = body["next_cursor"]
    if not body["results"]:
        time.sleep(5)

print(body["status"], body["coverage"])
print(len(job_urls), "job URLs")
print("\n".join(job_urls[:3]))
```

```text theme={"dark"}
completed {'pages_fetched': 1, 'pages_failed': 0, 'items_found': 50}
50 job URLs
https://job-boards.greenhouse.io/gitlab/jobs/8883003002
https://job-boards.greenhouse.io/gitlab/jobs/8565469002
https://job-boards.greenhouse.io/gitlab/jobs/8855883002
```

`depth: 1` reads the links on the board itself. The crawl fetched one page, so it cost one Fetch request. The same pattern works on any careers page: set `include_patterns` to the path its job links share.

## Get every job's details with Batch

Send the job URLs to one [Batch](/batch/developer-guide-restapi) job and pick the fields with `css_extractor`. On Greenhouse boards, the title is the `h1` and the location is `.job__location`:

```python Python (Requests) theme={"dark"}
import json, time
import requests

API = "https://async.api.zenrows.com/v1"
headers = {"X-API-Key": "YOUR_ZENROWS_API_KEY"}

job_urls = [
    "https://job-boards.greenhouse.io/gitlab/jobs/8883003002",
    "https://job-boards.greenhouse.io/gitlab/jobs/8565469002",
    "https://job-boards.greenhouse.io/gitlab/jobs/8855883002",
]  # or the job_urls list from the crawl above

selectors = {"title": "h1", "location": ".job__location"}

job = requests.post(f"{API}/jobs", headers=headers, json={
    "type": "regular",
    "status": "closed",
    "zenrows_params": {"mode": "auto", "css_extractor": json.dumps(selectors)},
    "tasks": [{"url": url} for url in job_urls],
}, timeout=60).json()
job_id = job["job_id"]

while True:
    run = requests.get(f"{API}/jobs/{job_id}", headers=headers, timeout=60).json()["latest_run"]
    if run["status"] in ("completed", "failed", "stopped"):
        break
    time.sleep(10)
print(run["status"], run["stats"])

results = requests.get(f"{API}/jobs/{job_id}/results", headers=headers,
                       params={"status": "successful"}, timeout=60).json()
for row in results["results"]:
    print(requests.get(row["result_url"], timeout=60).json())
```

```text theme={"dark"}
completed {'total': 3, 'completed': 3, 'successful': 3, 'failed': 0, 'spend': {'credits': 3, 'cost': 0.003}}
{'location': 'Remote, United States', 'title': 'Senior Product Manager, CRM & GTM Systems'}
{'location': 'Remote, United States', 'title': 'Director, Analytics Engineering'}
{'location': 'Remote, US', 'title': 'Senior AI Engineer'}
```

Add `.job__description` to the selectors for the full posting text, or set `response_type: "markdown"` instead of `css_extractor` to feed each posting to an LLM.

## Customize it

| Change | How |
| - | - |
| Another role or city on Indeed | Change `q` and `l` in the search URL |
| The next page of Indeed results | Add `start=10`, `start=20` to the search URL |
| Jobs from another country | Use that country's Indeed site with a matching `proxy_country`, see [Proxy country](/fetch/features/proxy-country) |
| Many searches at once | Put one search URL per task in a [Batch](/academy/scrape-from-a-list-of-urls) job with `extract=auto` in `zenrows_params` |
| New postings every morning | Run the crawl and batch on a [schedule](/academy/batch/scheduled-jobs) |
| Postings as markdown for an LLM | `response_type=markdown`, see [Markdown for LLMs](/academy/fetch/markdown-for-llms) |

## Troubleshooting

* **`job_listings` is shorter than the page you see**: Indeed shows a page of cards per request. Request the next pages with `start=10`, `start=20`.
* **`REQS004` with an "Available fields" list**: the field names differ on this kind of page. Pick names from the list in the error, or omit `extract_fields`. See [REQS004](/api-error-codes#REQS004).
* **The crawl returns no job URLs**: the pattern doesn't match the board's links. Open the board, copy one job link, and use the path segment it shares with the others.
* **A Batch task comes back empty**: that page uses different markup. Open it, adjust the selector, and rerun the failed tasks, see [Retry failed requests](/academy/retry-failed-requests).

## Related recipes

<CardGroup cols={2}>
  <Card title="Crawl a website from a start URL" icon="sitemap" href="/academy/scrape-and-crawl-from-a-seed-url">
    Discover every page behind one start URL.
  </Card>

  <Card title="Scrape a list of URLs" icon="list" href="/academy/scrape-from-a-list-of-urls">
    Batch jobs from the API, SDKs and CLI.
  </Card>

  <Card title="Get structured JSON from any page" icon="wand" href="/academy/extract/auto-extraction">
    How extract=auto and prepared domains work.
  </Card>

  <Card title="Detect changes on any page and get alerted" icon="bell" href="/academy/pipelines/change-detection">
    Get alerted when new postings appear.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.