# Google Jobs Scraper — Descriptions, Apply Links, Salaries (`foxlabs/google-jobs-scraper`) Actor

Scrape Google Jobs by keyword, location and country: title, company, location, posted date, full description, direct apply links, salary, employment type, highlights, the employer's domain and ATS. 10 jobs per search, widened with related searches and de-duplicated.

- **URL**: https://apify.com/foxlabs/google-jobs-scraper.md
- **Developed by:** [Berkan Kaplan](https://apify.com/foxlabs) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 jobs

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Google Jobs Scraper — Descriptions, Apply Links, Salaries

Get the jobs Google shows in **Google Jobs** (the Jobs tab of Google Search) for any keyword, location and country. One row per job with the **title, company, location, posted date, job description** (the full text in the US; Google shows only a preview in some countries, flagged), **direct apply links** (LinkedIn, Indeed, the employer's careers site…), **salary** where Google shows it, **employment type, highlights** (qualifications, responsibilities, benefits), the **employer's own domain** and the **ATS** behind the posting (Lever, Greenhouse, Ashby, Workday…).

No Google account, no API key, no browser. Requests go through Apify's Google SERP proxy.

### Quick start (API)

```bash
curl -X POST "https://api.apify.com/v2/acts/foxlabs~google-jobs-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"queries": ["registered nurse"], "locations": ["Texas"], "maxJobsPerQuery": 20}'
```

### What you get

| Group | Fields |
|---|---|
| Job | `title`, `companyName`, `location`, `postedText` ("2 days ago"), `postedAt` (YYYY-MM-DD), `postedDaysAgo`, `employmentType` (FULL\_TIME, PART\_TIME, CONTRACTOR, INTERNSHIP, TEMPORARY), `employmentTypeText`, `isRemote` |
| Pay | `salaryText` (as Google shows it), `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryCurrencyInferred`, `salaryPeriod` (HOUR, DAY, WEEK, MONTH, YEAR) |
| Apply | `applyUrl` (Google's first link for the job, never moved, tracking parameters removed), `applyOptions[]` (the job's apply links: source, URL, domain, `shownByGoogle`, `googleRank`, `matchesEmployer`, `evidence`, `employerInUrl`, `titleInUrl`), `otherEmployerOptions[]` (links Google grouped under the job that are another company's own ATS page or careers site — see below), `via` (the source Google credits) |
| Employer | `companyDomain` (the employer's own domain, only when it carries the company's name; never a job board, ATS or hosting platform), `atsProvider` + `atsUrl`, `companyLogoUrl` |
| Content | `description`, `descriptionIsPreview`, `qualifications[]`, `responsibilities[]`, `benefits[]`, `otherHighlights[]`, `perks[]` ("Health insurance", "No Degree Mentioned", "Work from home") |
| Context | `jobId` (Google's job ID, unique per row), `googleJobsUrl`, `query`, `searchLocation`, `country`, `language`, `searchText` (the Google search that found the job), `position`, `scrapedAt`, `error` |

#### Sample output

A real row from a test search on 2026-09-27, `software engineer` in New York, NY, as the current version reads it (trimmed: 3 of the job's 8 apply links shown):

```json
{
  "title": "Software Engineer, Systems ML - Compilers",
  "companyName": "Meta",
  "location": "New York, NY",
  "postedText": "2 days ago",
  "postedAt": "2026-09-25",
  "employmentType": ["FULL_TIME"],
  "isRemote": false,
  "salaryText": "183,997–257,000 a year",
  "salaryMin": 183997,
  "salaryMax": 257000,
  "salaryCurrency": "USD",
  "salaryCurrencyInferred": true,
  "salaryPeriod": "YEAR",
  "applyUrl": "https://www.linkedin.com/jobs/view/software-engineer-systems-ml-compilers-at-meta-4470554281",
  "applyOptions": [
    { "source": "LinkedIn", "url": "https://www.linkedin.com/jobs/view/software-engineer-systems-ml-compilers-at-meta-4470554281", "domain": "linkedin.com", "shownByGoogle": true, "googleRank": 1, "matchesEmployer": true, "evidence": "url-employer", "employerInUrl": null, "titleInUrl": true },
    { "source": "ZipRecruiter", "url": "https://www.ziprecruiter.com/c/Meta/Job/Software-Engineer,-Systems-ML-Compilers/-in-New-York,NY?jid=3441938ed51d43e7", "domain": "ziprecruiter.com", "shownByGoogle": true, "googleRank": 2, "matchesEmployer": true, "evidence": "url-employer", "employerInUrl": null, "titleInUrl": true },
    { "source": "talents.vaia.com", "url": "https://talents.vaia.com/companies/fiverr/software-engineer-search-engine-systems-38273373/", "domain": "talents.vaia.com", "shownByGoogle": false, "googleRank": 8, "matchesEmployer": false, "evidence": "url-employer", "employerInUrl": "fiverr", "titleInUrl": false }
  ],
  "otherEmployerOptions": [],
  "via": "LinkedIn",
  "description": "Reality Labs (RL) focuses on delivering Meta's vision through Virtual Reality (VR), Augmented Reality (AR) and Wearable AI Devices. …",
  "descriptionIsPreview": false,
  "qualifications": [
    "Bachelor's degree in Computer Science, Computer Engineering, relevant technical field, or equivalent practical experience",
    "3+ years of experience in developing compilers, toolchains, or code optimization software"
  ],
  "perks": ["Health insurance"],
  "jobId": "UmM1Ljk7hQKcMQZZAAAAAA==",
  "query": "software engineer",
  "searchLocation": "New York, NY",
  "country": "us"
}
```

### How it works

- **10 jobs per Google search.** Google Jobs shows 10 jobs per search, and the search page has no page 2 (asking for `start=10` returns "no Jobs matches"). To collect more, the Actor runs the related searches Google lists on the page and role-neutral variants ("… full time", "… senior", "… no degree"), removes duplicates by Google's job ID and stops when new searches stop finding new jobs. It does not promise "every job for a query"; measured runs are below.
- **What a location search returns (`remoteJobs`).** Google mixes nationwide remote jobs — location "Anywhere" — into location searches, and the widening searches bring more of them: in a platform test of "software engineer" in New York, NY, 11 of 50 jobs were "Anywhere" (1 among Google's own first 10 results, 10 from the widening searches, most from "… part time"). So:
  - `auto` (default): **with a location, "Anywhere" jobs are left out** — a New York search returns jobs Google lists in New York (or in several places, one of them shown, e.g. "McLean, VA (+1 other)"). On the platform, run `mroabOwxMJw1DXn5X` returned 50 jobs: 45 in New York City, 4 listed in several places, 1 in Newark, NJ; 0 "Anywhere" jobs, 15 left out and counted in `SOURCE_REPORT` (`filteredOut.remoteAnywhere`). Without a location, remote jobs are kept.
  - `include`: keep them, also in location searches (and run "… remote" as a widening search).
  - `exclude`: always leave them out. `only`: search for remote jobs and keep only those (location "Anywhere", a work-from-home label, or a remote word such as "Remote" or "Homeoffice" in the title).
  - Leaving remote jobs out means more searches to reach `maxJobsPerQuery` (the same New York search took 10 searches for 50 jobs, 8 before `remoteJobs` existed; 2 instead of 1 for 10 jobs). Google's remote label is recognised as "Anywhere" (English), "Beliebiger Ort" (German, seen in Berlin runs `zbISyf1BEvtKLFV5t` and `FNFvMce2rnGNcY7LP`) and "Qualquer lugar" (Portuguese, 10 of 10 jobs in a Brazilian remote search). No such label was seen for the other languages the Actor reads, so none is assumed: Spanish and Dutch test searches showed a city for remote jobs, French and Italian remote searches returned no jobs, and Poland and Turkey returned ordinary web results instead of Google Jobs (2 of 2 searches each, 2026-09-28). If Google uses another label there, `auto` and `exclude` keep those jobs.
- **Direct apply links.** The links Google shows are its own redirects; the Actor reads the direct posting URLs Google embeds in the page. `applyUrl` is Google's first link for the job — it is never moved to `otherEmployerOptions`, so it is only empty when Google lists no link at all (0 of 445 jobs in the 0.1.2 test runs).
- **Links that belong to another company are set apart — only on strong evidence.** Google groups postings it considers the same job, and the group sometimes holds other employers' jobs: in one test, Google listed Paramount's and Capgemini's careers sites and Palantir's Lever page as "Apply" links for an Uber job, in Uber's own job panel. By default every link Google lists stays in `applyOptions`. A link moves to `otherEmployerOptions` only when it is **another company's own ATS page** (a Lever/Greenhouse/Workday… tenant with another company's name) **or its own careers site** (a `careers.`/`jobs.`-style sub-domain outside .org/.edu/.gov and similar domains, or a corporate careers path such as `/careers/…` or `/us-en/jobs/…`), and the link does not show this job's title (for a site recognised only by its careers path, the link must show a different title). Google's first link never moves. Links on the job boards the Actor recognises stay, also when their URL names another company (they are flagged, see below), and so do links on sites it cannot place. Each link carries:
  - `matchesEmployer`: `true` when the link's domain, ATS tenant, sub-domain or URL names this company; `false` when the URL names another company (an ATS tenant, or a job-board URL such as LinkedIn "…-at-baseten-…" or ZipRecruiter "/c/Medix/") or the link is another company's careers site as defined above; `null` when the Actor finds no employer name in the URL — job boards whose URL format it does not read (`indeed.com/viewjob?jk=…`; the company part of StepStone URLs is not read either), ATS links without a tenant and sites it cannot place.
  - `evidence`: `employer-domain`, `employer-hosted-page` (a sub-domain named after this company on a hosting or careers platform, e.g. remotehub.my-board.org), `ats`, `url-employer`, `url-mentions-employer` (the URL path contains the company's name), `job-board`, `other-company-site` or `unknown-site`. `employerInUrl` is set only when `matchesEmployer` is `false`: the other company's name as found in the URL (ATS tenant or board slot; LinkedIn's keeps the job number, e.g. "baseten-4380548115") — for `other-company-site` only the site's domain name (e.g. "paramount").
  - `titleInUrl`: `true` when every word of the job title appears in the link, `false` when some do not, `null` when there is nothing to compare (titles of fewer than three words, links with almost no words); numbers are ignored, and work-mode words ("Remote", "Remoto", "Homeoffice") need not appear. A board link that names another employer but shows this job's title is usually the same job posted under a parent company or agency name (Tenet Healthcare for Valley Baptist).
  - `shownByGoogle`: Google showed an "Apply on …" button for the link (it lists some links without one); `googleRank` is its place in Google's list.
  - Measured on 445 jobs from 12 platform test runs (build 0.1.2, re-read with the current rules and checked against an independent review): links set apart went from 213 to 40, and jobs whose first Google link was set apart from 2 to 0 (one of them had been left without an `applyUrl`). Of 77 links the review labelled by hand, the 19 still set apart are all other companies' (Paramount, Capgemini, HCA Healthcare, Comcast, staffing agencies…); none of the 50 job-board links and not the one employer's own site among them is set apart. 7 other-company links stay: 5 whose URL carries no title to compare (for example `voyagesolutions.com/jobs/469134/`) and 2 staffing-agency pages with a bare `/job/…` path, the pattern job boards use too. The other 21 links set apart are ATS pages whose tenant is another company (Palantir, Blue Origin, Comcast…; checked by the tenant name). Part of this relies on the Actor's list of job boards, which was extended from the same review; a board it does not know that runs on a `jobs.` sub-domain can still be set apart (9 of the 50 labelled board links without the new entries).
- **The ATS** is read only from the job's own link or from an ATS link whose tenant carries the company's name.
- **Only delivered jobs are charged.** A search with no result, or where the filters removed everything, leaves one free row with an `error` explaining why. The `SOURCE_REPORT` record in the key-value store lists every search used and counts the duplicates and the filtered jobs (per reason) for each query.

### Input & filters

| Input | What it does | Default |
|---|---|---|
| `queries` | Job searches, one per line ("software engineer", "registered nurse", "Softwareentwickler") | — |
| `locations` | City, state or region, one per line. Each search runs once per location. Empty = no location in the search: Google then shows nationwide remote jobs plus jobs near the proxy's location (a US test, run `fqRAZzRKpIkoJsWtO`: 16 "Anywhere", 10 of the other 14 in Colorado/Wyoming/New Mexico). Add locations for real coverage | — |
| `country` | Which Google Jobs to search (39 countries). Poland and Turkey were removed on 2026-09-28: Google showed ordinary web results there instead of Google Jobs (2 of 2 test searches each). A country where Google shows no jobs ends with the free explanatory row | us |
| `language` | Interface language; empty = the country's main language | — |
| `maxJobsPerQuery` | Jobs per search and location (up to 300). The Console form starts at 10 | 50 |
| `datePosted` | `any`, `today`, `3days`, `week`, `month`. Jobs Google shows without a date are left out when a window is set | any |
| `employmentType` | Keep only these types. Jobs without a type label are kept | all |
| `remoteJobs` | `auto`, `include`, `exclude`, `only` — what to do with remote jobs Google lists as "Anywhere" (see How it works). `auto` leaves them out when a location is set and keeps them otherwise | auto |
| `includeDescription` / `includeHighlights` | Turn the long text fields off for smaller datasets | on |
| `maxSearchesPerQuery` | Upper bound on Google searches used to widen one search (each returns up to 10 jobs) | 30 |

Invalid input (unknown country, date window or employment type, a non-integer limit) is rejected by Apify before the run starts: the API answers HTTP 400 with the reason and no run is created. Values the input form cannot check (for example a malformed `language` code) stop the run with the reason in its status message.

### Example inputs (copy & paste)

Nurses in Texas, full-time only, last 7 days:

```json
{ "queries": ["registered nurse"], "locations": ["Texas"], "employmentType": ["FULL_TIME"], "datePosted": "week", "maxJobsPerQuery": 100 }
```

Software jobs in Berlin, German Google:

```json
{ "queries": ["Softwareentwickler"], "locations": ["Berlin"], "country": "de", "maxJobsPerQuery": 50 }
```

Remote data jobs across the US, without descriptions (smaller dataset; run time is unchanged — it depends on the number of Google searches):

```json
{ "queries": ["data analyst", "data engineer"], "remoteJobs": "only", "includeDescription": false, "includeHighlights": false, "maxJobsPerQuery": 50 }
```

Several cities in one run (duplicates across searches are delivered once):

```json
{ "queries": ["electrician"], "locations": ["Houston, TX", "Dallas, TX", "Austin, TX"], "maxJobsPerQuery": 50 }
```

### Use cases

- **Recruiting and staffing:** who is hiring for a role in an area, with the direct link to the posting and the date.
- **Sales prospecting (hiring signals):** companies hiring for a role, their domain (`companyDomain`) for enrichment and the ATS they use.
- **Job boards and HR tech:** full descriptions, highlights, pay and type, de-duplicated by Google's job ID.
- **Labour-market research:** pay ranges and employment types by role, city and country.

### Performance & coverage

Measured on the Apify platform on 2026-09-28 (1 GB memory, build 0.1.2, default `remoteJobs: auto`). 0.1.3 changes how apply links, company domains, pay and remote labels are read; the searches are the same, except that the same words in another order no longer run as a second search:

| Search | Jobs | Google searches | Time | Run |
|---|---|---|---|---|
| `software engineer`, New York, NY | 50 | 10 | 171 s | `mroabOwxMJw1DXn5X` |
| `registered nurse`, Texas | 50 | 7 | 160 s | `hMbq3ZsZCiAt0WXHd` |
| `Softwareentwickler`, Berlin (Germany) | 30 | 4 | 127 s | `zbISyf1BEvtKLFV5t` |
| `software engineer`, New York, NY, 10 jobs (the form's starting input) | 10 | 2 | 37 s | `AUpPjxT74NdQKh9eZ` |

50 jobs took 160–171 seconds in these runs (103 s for 50 Berlin jobs in run `FNFvMce2rnGNcY7LP`). Run time depends mostly on Google's response time through the proxy: a single search took up to 66 seconds, and the median per run ranged from 3 to 43 seconds (18 s New York, 22 s Texas, 43 s Berlin). Peak memory in the four runs above was 109–236 MiB. No run of that build returned a duplicate `jobId` (12 runs, 445 jobs). The New York runs left out 15 and 1 "Anywhere" jobs and searched further to reach the requested number. Before the platform runs, a local test capped at 8 searches returned 39 jobs — 35 in New York City, 3 listed in several places with another city shown, 1 in Newark, NJ — and left out 11 remote ones; on the platform, run mroabOwxMJw1DXn5X reached 50 jobs with 10 searches and left out 15.

### Data quality

Share of jobs with a value in the three platform runs above (build 0.1.2, n = 50 / 50 / 30; company domain recounted with the 0.1.3 rules on the same rows):

| Field | SWE, New York | Nurse, Texas | Berlin (Germany) |
|---|---|---|---|
| title, company, location, source, apply link | 100% | 100% | 100% |
| posted date | 86% | 84% | 67% |
| description | 100% full | 98% full | 100% **preview** |
| employment type | 100% | 100% | 100% |
| salary | 44% | 18% | 37% |
| qualifications / responsibilities | 98% / 96% | 88% / 94% | — |
| company domain | 22% | 34% | 10% |

- **Posted date:** we aimed for 90% of jobs with a posting date, but Google itself shows one on only 50–93% of jobs in the test runs without a date filter (86% New York, 84% Texas, 67% Berlin; 50% in a second Berlin run, 93% in Paris). For the other jobs there is no date anywhere on the page — not on the job card, not in the job's details (checked, for example, on Plaid's and Browserbase's New York jobs, whose panels show salary and job type but no date). Those rows have `postedAt: null`; the date is never estimated. Google's "30+ days ago" is stored as 30 days, a lower bound (not seen in the test runs). Where Google shows a date, it is parsed in English, German, French, Spanish, Portuguese, Italian, Dutch, Polish and Turkish (checked on real pages in English, German, French, Spanish, Portuguese and Dutch).
- **Descriptions:** Google shows the full description in the US (and in a Brazilian test search: 9 of 10 full, 1 without a description). In Germany and France it showed only a preview of up to 400 characters for every job in the 0.1.2 runs (30 of 30 and 50 of 50 in Berlin, 30 of 30 in Paris; an earlier Berlin run had 10 of 30 full), and so did one test search each in Spain and the Netherlands (10 of 10). Those rows have `descriptionIsPreview: true`.
- **Salary:** US pay labels have no currency sign ("183,997–257,000 a year"); the country's currency is filled in and `salaryCurrencyInferred` is `true`. Thousands written apart from the number are read: "65 k € à 85 k € par an" gives 65000–85000 (also "55 Tsd. €", "60 mil €", "R$ 4 mil por mês"). Build 0.1.2 read 7 of the 8 pay labels in a Paris run (`wfC9gQehi27uvlAaW`) 1,000 times too small ("65 k €" as 65); fixed in 0.1.3, and no other of the 206 pay labels in the test runs changed.
- **Company domain:** only the employer's own domain — the domain of an apply link whose name matches the company's name (uber.com, homedepot.com; a careers domain such as capitalonecareers.com or tdbank.jobs counts). Initials and acronyms count (bswhealth.com for Baylor Scott & White Health, gm.com for General Motors); a shared generic word ("health") alone does not. It is `null` when the only candidates are job boards, ATS or careers platforms, hosting services or link aggregators — also for companies that are themselves job boards (LinkedIn, France Travail). In the 0.1.2 test runs the independent review found 14 of 94 values wrong (ALL IN GROUP → indeed.com, RemoteHub → my-board.org, Thermon → applicantpool.com…); the 0.1.3 rules give 76 values on the same rows, none of them among the 14 (the other 4 removed were job-board companies).
- **Apply links checked:** 12 apply links on 12 different sites (employer career sites, LinkedIn, Built In, StepStone, the German employment agency) were opened; all 12 showed the job's title and company.

### Pricing

**$3 per 1,000 jobs ($0.003 per job).** One `job` event per delivered job. Status rows (no results, everything filtered) are free. Each run also has Apify's Actor start event ($0.00005 per GB of memory; the default 1 GB is one event). Current prices are also on the Pricing tab.

### FAQ

**Why do I get fewer jobs than I asked for?** Google shows 10 jobs per search; the Actor widens with related searches until new searches bring no new jobs. Narrow or unusual searches run dry sooner. `SOURCE_REPORT` shows how many searches ran and how many jobs were duplicates.

**Are the apply links direct?** Yes, `applyUrl` and `applyOptions[].url` are the posting URLs Google embeds, not Google redirects, with `utm_*` parameters removed.

**I searched "New York" — why are there no remote jobs?** With a location, the default (`remoteJobs: "auto"`) leaves out jobs Google lists as "Anywhere" ("Beliebiger Ort" in German, "Qualquer lugar" in Portuguese), so the results are jobs placed in your location. Set `remoteJobs` to `include` to add them, or `only` for remote jobs only. `SOURCE_REPORT` shows how many were left out.

**Why are some of Google's apply links in `otherEmployerOptions`?** Google groups postings it considers the same job and sometimes includes other employers' jobs in that group. Only links with strong evidence are kept apart: another company's own ATS page or careers site that does not show this job's title. Google's first link is never kept apart, and links on job boards stay in `applyOptions` even when their URL names another company (then `matchesEmployer` is `false`), so check that field if you need only this employer's postings. The links kept apart are still in the dataset for review.

**Why is `companyDomain` often empty?** It is filled only from an apply link on the employer's own domain, whose name matches the company's. Job boards, ATS and careers platforms and hosting services are never used, so jobs posted only on boards (LinkedIn, Indeed) have none, and neither do companies that are themselves job boards.

**Can I get only new jobs every day?** Use `datePosted: "today"` on a daily schedule; jobs Google shows without a date are left out when a window is set.

### Troubleshooting

- **"Google Jobs shows no jobs for this search in this country"**: try a broader search or check that Google Jobs covers the country.
- **"none matched the filters"**: the date, type or remote filter removed every job Google showed; the message lists how many each filter removed.
- **"Apify Google SERP proxy is not available to this run"**: the Google SERP proxy must be enabled on your Apify account (Settings → Proxy).

### Notes, limits & legal

- Data is what Google Jobs publicly shows at run time; employers' postings change daily.
- Google may change its page layout without notice; fields can then come back empty until the Actor is updated. If you see that, open an issue with the run ID.
- Only publicly posted job ads are collected. Descriptions can contain whatever contact details the employer published.
- Google, Google Jobs and all job board names are trademarks of their owners; this Actor is not affiliated with them.

### Support

Open an issue on the Issues tab with the run ID and the input, or write to info@foxlabs.com.tr.

### Changelog

#### 0.1.5 — 2026-09-28

- Poland and Turkey removed from `country`: Google showed ordinary web results there instead of Google Jobs (2 of 2 test searches each). An input with `pl` or `tr` is now rejected before the run starts.

#### 0.1.4 — 2026-09-28

- Pricing: $3 per 1,000 jobs, status rows free.

#### 0.1.3 — 2026-09-28

- Apply links: every link Google lists stays in `applyOptions` unless it is another company's own ATS page or careers site without this job's title; Google's first link (`applyUrl`) never moves. Measured on 445 jobs: links set apart 213 → 40, none of them a job board or the employer's own site in the independently labelled sample.
- `evidence` values: `other-site` is replaced by `other-company-site` (another company's own careers site), `job-board` or `unknown-site`; new `employer-hosted-page`.
- `companyDomain` only from the employer's own domain; never a job board, ATS, careers or hosting platform (14 wrong values of 94 in the test runs → 0).
- Pay: thousands written apart from the number ("65 k €", "55 Tsd.", "60 mil", "R$ 4 mil") and Portuguese "por mês" are read.
- Remote: "Beliebiger Ort" (German) and "Qualquer lugar" (Portuguese) are recognised as "Anywhere"; "Homeoffice" in a title marks the job remote.
- Employment type: French "Prestataire" and Portuguese "Prestador de serviços" are CONTRACTOR.
- A job past the per-search limit is no longer marked as seen, so a later location or query can still deliver it; the same search words in another order run once.
- README corrections from an independent review; the claim that the Actor is monitored with test searches was removed (no monitoring runs at the moment). See CHANGELOG.md.

#### 0.1 — 2026-09-28

First version. See CHANGELOG.md.

# Changelog

This Actor's version history is a separate document: https://apify.com/foxlabs/google-jobs-scraper/changelog.md

# Actor input Schema

## `queries` (type: `array`):

One search per line, as you would type it in Google: a job title, skill or role ("software engineer", "registered nurse", "Softwareentwickler"). Each search runs once per location below.

## `locations` (type: `array`):

City, state or region, one per line ("New York, NY", "Texas", "Berlin"). Leave empty to search the whole country.

## `country` (type: `string`):

Which Google Jobs to search: the country's Google domain and job market. Google Jobs is not available in every country; an unsupported one returns an explanatory empty row.

## `language` (type: `string`):

Two-letter interface language (en, de, fr…). Empty = the country's main language. Dates, pay and job types are read in English, German, French, Spanish, Portuguese, Italian, Dutch, Polish and Turkish.

## `maxJobsPerQuery` (type: `integer`):

Google shows 10 jobs per search; beyond that the Actor runs related searches ("… full time", "… senior") and removes duplicates, until this number or until new searches stop finding new jobs. The form starts at 10 for a quick first run.

## `datePosted` (type: `string`):

Keep only jobs posted in this window (from Google's "5 days ago" labels). Jobs Google shows without a date are left out when a window is set.

## `employmentType` (type: `array`):

Keep only these types. Jobs whose type Google does not show are kept.

## `remoteJobs` (type: `string`):

Google mixes nationwide remote jobs (location "Anywhere"; "Beliebiger Ort" on German pages) into location searches. Auto: with a location they are left out, so a New York search returns jobs located in New York; without a location they are kept. Include: keep them. Exclude: leave them out. Only: search for remote jobs and keep only those (location "Anywhere", a work-from-home label, or a remote word such as "Remote" or "Homeoffice" in the title).

## `includeDescription` (type: `boolean`):

The job description as Google shows it. Some countries (Germany and France, measured) show only a preview of up to 400 characters; those rows have descriptionIsPreview = true. Turning it off makes the dataset smaller, not the run faster.

## `includeHighlights` (type: `boolean`):

Qualifications, responsibilities and benefits as Google lists them (where Google shows highlights).

## `maxSearchesPerQuery` (type: `integer`):

Upper bound on the searches used to widen one search. Each search returns up to 10 jobs.

## Actor input object example

```json
{
  "queries": [
    "software engineer"
  ],
  "locations": [
    "New York, NY"
  ],
  "country": "us",
  "maxJobsPerQuery": 10,
  "datePosted": "any",
  "employmentType": [],
  "remoteJobs": "auto",
  "includeDescription": true,
  "includeHighlights": true,
  "maxSearchesPerQuery": 30
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "queries": [
        "software engineer"
    ],
    "locations": [
        "New York, NY"
    ],
    "maxJobsPerQuery": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("foxlabs/google-jobs-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "queries": ["software engineer"],
    "locations": ["New York, NY"],
    "maxJobsPerQuery": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("foxlabs/google-jobs-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "queries": [
    "software engineer"
  ],
  "locations": [
    "New York, NY"
  ],
  "maxJobsPerQuery": 10
}' |
apify call foxlabs/google-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,foxlabs/google-jobs-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/ACYrRkw0ed4vJfZ5h/builds/wslhaiVOJP0l1QALM/openapi.json
