# Google Jobs Scraper: EU Jobs, Pay per Job (`olliolli/google-jobs-search`) Actor

Search Google Jobs by role, city and country. Export vacancies, salary, application links and ATS boards. Track newly seen jobs across runs. Pay per delivered job.

- **URL**: https://apify.com/olliolli/google-jobs-search.md
- **Developed by:** [Tom J](https://apify.com/olliolli) (community)
- **Categories:** Jobs, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 job listings

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 Search** finds public job listings by role, city and country, with application links and an optional feed of newly seen jobs. No Google login or source API key is required.

### What you can do

- Search multiple roles in one country, using a local interface language.
- Export title, employer, the posting's actual location, available salary/date/type, description or preview, and application links.
- Recognize supported ATS application links and pass `atsBoardUrl` to `olliolli/ats-job-feed` for a company-specific feed.
- Return only jobs not previously delivered under your monitor state, with an optional webhook summary.

### How to use it in 3 steps

1. Enter job titles, a city and country. Prefer local-language terms.
2. Choose a result cap and optional date/type filters.
3. Run, inspect OUTPUT diagnostics, then export the dataset as JSON, CSV or Excel.

```json
{"queries":["software developer"],"location":"Gent","country":"BE","maxJobsPerQuery":10}
```

### Tested country coverage

On 25 October 2026, five role searches per market/language returned jobs in **BE (Dutch and French), NL, DE, FR, ES, IT and US**: 40/40 searches, 120 delivered rows with a three-job cap. Belgium and the Netherlands sometimes needed the alternate local-language search. These are a dated test sample, not an availability guarantee. GB is selectable but was not part of this live matrix.

The basic five-country acceptance set (BE, NL, DE, FR, US) had zero failed queries out of 25. Earlier development runs exposed timeouts, redirects and empty Jobs modules; those observations remain in the test report rather than being treated as successful searches.

### Input

Up to 50 queries; one country/location per run. Language defaults to the country's language. Belgian searches default to Dutch, or French for recognized Walloon cities and Bruxelles; set `language: "fr"` explicitly when needed.

`maxJobsPerQuery` defaults to 10 for a small, predictable first run. It is a cap, not a promised count; raise it to 100 or up to 200 when needed. A larger search expansion can take several minutes: a Gent test with a cap of 100 returned 25 unique jobs in about 102 seconds. With `expandSearch: true` and a cap above 10, the Actor also searches local-language refinements for employment type, date and remote work. It can follow compatible filter links found on the source page. If the first page lacks a Jobs module, one alternate local-language query is tried when search expansion is enabled. At most ten pages per query and 60 source requests per run by default. This is bounded search expansion, **not exhaustive pagination**. Use several specific roles/cities for broader coverage.

`postedWithin` applies a strict filter using Google's estimated posting age; unknown dates are excluded. `employmentType` similarly excludes unknown/nonmatching types. These filters can produce fewer rows than Google displays. `location` steers the search, but is not an exact geographic boundary. Exact radius filtering is unsupported and `radiusKm` input is rejected.

### Output

One row per delivered, deduplicated job. `location` is the posting location; `searchLocation` is your input. `postedAtEstimate` is derived from Google's relative label, not a verified publication timestamp. Salary is retained only when shown. Unknown work-from-home status is null.

`descriptionIsPreview` identifies a short source preview. Full descriptions are available only when Google supplies them. Missing optional fields remain empty. Descriptions and highlights stop at a detected contact section; recognizable email/phone patterns are removed. This may shorten a description; this is not applicant or recruiter-profile collection.

`applyOptions` contains up to ten public application links per job. Google redirect responses are resolved without fetching the destination page. An unresolved link is retained with `isGoogleRedirect: true`; `applicationLinksTruncated` marks a source with more than ten options. We do not visit LinkedIn, Indeed, Glassdoor or other job-board detail pages. `atsBoardUrl` is populated only when a supported board identifier also matches the employer name. Other recognized links remain in `atsBoardCandidates` for review. Google sometimes groups an unrelated application link with a vacancy; name matching reduces mistaken employer-feed handoffs but is not a live board verification. Use ATS Job Feed to verify and retrieve that board.

OUTPUT includes query outcomes, page counts, duplicates/filtered rows, source requests, connection attempts, recovery events, delivered counts, stop reasons and unchecked queries. A missing Google Jobs module is reported separately from a transport/parser failure. An empty result does not mean that no employer in the area is hiring.

### How much does it cost?

Base prices:

| Event | USD |
|---|---:|
| Delivered job listing | 0.004 |
| Standard start per allocated GB, minimum one | 0.00005 |

Ten delivered jobs at up to 1 GB cost $0.04005; 100 cost $0.40005. No separate search/page fee or residential-session event. The active Console pricing table is authoritative.

#### What you are not charged for

No job event for duplicates, filtered-out jobs, empty results or failed/blocked requests. A run that delivers nothing still has the small standard start event. We stop when the remaining event budget cannot pay for another result. There is no automatic dataset-item charge.

### Automate and schedule

Set `onlyNew: true` and a unique `stateName`, then use an Apify task and schedule. The stable job key combines normalized title, employer and posting location. That suppresses cross-query duplicates and previously delivered matches, but can merge different openings with the same title/company/location or treat a changed title/location as new. This is new-job monitoring, not closed-job or change tracking.

Do not overlap runs with the same state. Use a fresh state name to reset history. State stores hashed job keys and delivery timestamps. A webhook receives a compact summary only after delivery; failure is reported without automatic retry. The dataset remains authoritative.

### Integrate with any app

Python:

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('olliolli/google-jobs-search').call(
    run_input={'queries': ['software developer'], 'country': 'BE',
               'location': 'Gent', 'maxJobsPerQuery': 10},
    memory_mbytes=512, timeout_secs=180,
)
rows = client.dataset(run['defaultDatasetId']).list_items().items
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('olliolli/google-jobs-search').call({
  queries: ['software developer'], country: 'BE', location: 'Gent',
  maxJobsPerQuery: 10,
}, { memory: 512, timeout: 180 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Your platform token is an Apify credential, not a Google credential.

### MCP and agent workflows

An Apify-compatible MCP integration can invoke the Actor when your account has access. There is no separate MCP server in this Actor. Use small query lists and request/time limits for unattended agents.

### Good to know

Google's public Jobs interface is unofficial and can change or block access. The Actor uses Apify's HTTP Google SERP route first on google.com, with explicit country (`gl`) and language (`hl`). Network/temporary-server failures may use datacenter and then residential recovery, at most three connections in the run. Access blocks, CAPTCHA and consent barriers stop without escalation or clicking through. There is no user login, personal browser profile, fabricated consent cookie or CAPTCHA solving.

Country and language strongly affect whether Google shows the Jobs module. Empty fields and previews are source limitations. Search expansions may return nearby locations and related roles. No complete coverage or continuous availability guarantee is made.

### Privacy

Only public vacancy fields are retained, with contact redaction. No personal cookies, headers, proxy credentials, raw Google response or applicant/recruiter profiles are saved by the production Actor. Logs use short safe error reasons. State contains hashes and delivery dates. A webhook you supply receives job-title/company/location summaries.

### FAQ

#### Why are there fewer jobs than my limit?

Google exposes a limited sample. The cap is not a guarantee. Try more precise role/city combinations or enable search expansion.

#### Is this an official Google Jobs API?

No. It reads public search pages and is independent of Google.

#### Why is my description short?

Some markets show previews. The Actor marks them and never invents the missing text or scrapes linked job boards for enrichment.

#### Why did a run return no rows?

Inspect OUTPUT. The source may lack a Jobs module, all matches may be filtered/already seen, or the connection/parser may have failed. These are different outcomes.

#### Does onlyNew detect edited or closed jobs?

No. It suppresses previously delivered title/company/location keys. Use ATS Job Feed for changes on supported company boards.

#### Can I filter within an exact radius?

No. The city influences Google's results, but exact radius filtering is not supported.

#### Are apply links and ATS boards verified live?

Application URLs are decoded and cleaned without following them. ATS domains are recognized structurally. Use the linked board in ATS Job Feed for a live board check.

### Disclaimer

Independent research tool, not affiliated with Google or job boards. Respect source terms and your rights to process supplied queries and output. Data may be incomplete, stale or sampled.

### Need help?

Provide a run ID, safe OUTPUT reason and a non-sensitive sample input. Never share credentials, personal cookies or private webhook tokens.

# Actor input Schema

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

One job search per term. Use local-language titles for the best country coverage.

## `location` (type: `string`):

Added to the search query. Google can return jobs outside this area; this is not a strict geographic filter.

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

Country setting for Google Search (gl); all searches use google.com. Coverage is source-dependent.

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

Leave empty for the country default. Belgium uses Dutch, or French for recognized Walloon cities/Bruxelles. Override to choose explicitly.

## `postedWithin` (type: `string`):

Strict post-filter using estimated age. Jobs without a recognizable posting date are excluded when a window is selected.

## `employmentType` (type: `string`):

fulltime, parttime, contractor or internship. Strict filter; unknown types are excluded.

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

An upper limit, not a promised count. Coverage depends on Google and available search refinements.

## `expandSearch` (type: `boolean`):

When the limit exceeds 10, also search local-language employment/date/remote refinements and deduplicate. This is not exhaustive pagination.

## `onlyNew` (type: `boolean`):

Skip jobs already delivered under stateName. Do not overlap runs with the same state.

## `stateName` (type: `string`):

Required for onlyNew. Letters, numbers, underscores or hyphens, at most 50. A new name starts fresh.

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

Include the available text or preview. Text stops at detected contact sections and may be shortened.

## `webhookUrl` (type: `string`):

Receives a short summary of delivered jobs. Public HTTP(S) only; failed delivery is reported without retry.

## `maxRunSeconds` (type: `integer`):

Stops source work within this bound and reports incomplete queries.

## `maxRequests` (type: `integer`):

One request per fetched source page, including search refinements and recovery attempts.

## `residentialFallback` (type: `boolean`):

For network/server failures only: SERP → datacenter → residential. Maximum three connections per run. Access blocks stop without escalation.

## Actor input object example

```json
{
  "queries": [
    "software developer"
  ],
  "location": "Gent",
  "country": "BE",
  "postedWithin": "any",
  "maxJobsPerQuery": 10,
  "expandSearch": true,
  "onlyNew": false,
  "includeDescription": true,
  "maxRunSeconds": 600,
  "maxRequests": 60,
  "residentialFallback": true
}
```

# Actor output Schema

## `jobs` (type: `string`):

No description

## `diagnostics` (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 developer"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("olliolli/google-jobs-search").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 developer"] }

# Run the Actor and wait for it to finish
run = client.actor("olliolli/google-jobs-search").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 developer"
  ]
}' |
apify call olliolli/google-jobs-search --silent --output-dataset

```

## MCP server setup

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

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/Fm2HN47Sd33aLWv9x/builds/rPtCGEbLGdoGuxvp4/openapi.json
