# ge.ch Emploi Scraper: Geneva Canton Public Sector Jobs (`santamaria-automations/ge-ch-emploi-scraper`) Actor

Scrape Geneva canton public-sector jobs from ge.ch. Returns title, department, service, salary class, workload, CDI/CDD, publication date, deadline, apply link, reference number and full description (text, HTML, Markdown). No login, no proxy.

- **URL**: https://apify.com/santamaria-automations/ge-ch-emploi-scraper.md
- **Developed by:** [NanoScrape](https://apify.com/santamaria-automations) (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 $1.00 / 1,000 actor starts

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

## Geneva Canton Jobs Scraper (ge.ch): Public Sector Job Offers

Extract every open job offer from [ge.ch](https://www.ge.ch/offres-emploi-etat-geneve/liste-offres), the official employment portal of the Republic and Canton of Geneva (Etat de Genève). Each row gives you the French job title, the hiring department and service, the official salary class, the workload (taux d'activité), the contract type (CDI or CDD), the publication date, the application deadline, the apply link, the SIRH reference number and the full description as text, HTML and Markdown.

No login, no captcha and no proxy needed. The actor reads the public portal over plain HTTP, so runs are fast and cheap.

### What it does

- Reads the ge.ch offer list and each job page, and returns clean, structured rows.
- Matches your French keywords against the title, service, department and each offer's own mission and profile text, ignoring case and accents, because ge.ch has no keyword search of its own. Title and service matches are listed first. Generic lines such as "maitrise des outils informatiques" are ignored, so "informatique" returns IT jobs, not every job that wants Excel skills.
- Orders results by publication date, newest first, so a cap always keeps the latest offers. ge.ch has no sort control, so this is done by the actor on the full list (about 50 to 80 offers, all on one page).
- Filters by contract type, department, minimum salary class, activity domain and workload using the portal's own list filters, and by publication date (`postedWithinDays`) in the actor.
- Accepts ge.ch listing URLs and single job URLs, for example to check that a posting is still live.

### Pricing

$0.001 per run start, $0.003 per job returned (about $3 per 1,000), plus $0.005 per job enriched with full details (about $8 per 1,000 jobs with details).

A job whose page could not be read is billed the $0.003 job price only. Rows that fail to save are never billed.

New to Apify? Every account gets a free monthly platform credit, enough to read the whole Geneva portal many times over.

### Input

| Field | Type | Description |
|---|---|---|
| `searchQueries` | string\[] | French keywords, one query each (`informatique`, `enseignant`, `assistant`). Empty returns all open offers. |
| `maxResultsPerQuery` | integer | Cap per keyword or search URL. If empty it equals `maxResults` when that is set, otherwise 25. |
| `maxResults` | integer | Total cap. Leave empty to use `maxResultsPerQuery` times the number of queries. |
| `sortBy` | enum | `newest` (default), `oldest`, or `site` for the portal's own order. Newest and oldest are sorted by the actor from each job's publication date, not by ge.ch. |
| `includeJobDetails` | boolean | Default `true`. Adds the full description, profile, benefits, workplace, apply link and reference number. |
| `contractType` | enum | `any`, `CDI`, `CDD` or `apprentissage`. Applied by ge.ch (`type_contrat`). |
| `department` | enum | Only offers from one department, for example `finances` or `instruction`. Applied by ge.ch (`departement`). |
| `minFunctionClass` | integer | Only offers at or above this salary class, 4 to 33. Applied by ge.ch (`classe_fonction_min`). |
| `activityDomain` | enum | Domain of activity, `00` Management to `22` Public policy, for example `19` for information systems. Applied by ge.ch (`domaine_activite`). |
| `maxWorkPercentage` | enum | Offers whose workload starts at or below this percentage (10 to 100), for example `50` for part-time roles. Applied by ge.ch (`taux_activite_max`). |
| `postedWithinDays` | integer | Only offers published in the last N days. Applied by the actor on the publication date of each job page. |
| `searchUrls` | string\[] | ge.ch listing URLs. Each counts as one query. |
| `directUrls` | string\[] | Single ge.ch job page URLs. Always read as full detail pages. |
| `maxConcurrency` | integer | Job pages fetched in parallel, 1 to 10. Default 5. |

Everything has a default, so an empty input `{}` returns the 25 newest offers with full details. With only `maxResults` set (for example 200) you get up to that many offers, about 50 to 80 at most today.

Example input:

```json
{
  "searchQueries": ["informatique", "assistant"],
  "maxResultsPerQuery": 20,
  "sortBy": "newest",
  "includeJobDetails": true
}
```

### Output fields

- **Identity**: `id`, `title`, `job_url`, `source_url`, `source_platform`, `reference_number` and `reference_id` (SIRH number from the apply link)
- **Employer**: `company_name` (the department), `hiring_organization` (Etat de Genève), `department`, `department_url`, `service`
- **Place**: `location`, `workplace`, `city`, `canton` (`GE`), `country` (`CH`)
- **Dates**: `posted_at_datetime`, `posted_at`, `posted_at_text`, `updated_at`, `updated_at_text`, `application_deadline`, `application_deadline_text`, `valid_through`, `job_start_date`, `start_date_text`
- **Terms**: `contract_type` (CDI, CDD, apprentissage), `contract_type_note`, `remote_option`, `employment_type` (full-time, part-time, apprenticeship), `work_percentage_text`, `work_percentage_min`, `work_percentage_max`
- **Salary**: `salary_class`, `salary_class_number`, `salary_scale_url`
- **Description**: `description` (first 500 characters), `description_snippet` (300 characters or fewer, cut at a word boundary with an ellipsis), `description_full`, `description_html`, `description_md`, `experience_requirements` (profile), `job_benefits`, `observations`
- **Apply**: `apply_url`, `apply_platform`, `apply_instructions`
- **Contact**: `contact_name`, `contact_title`, `contact_email`, `contact_phone`, `contact_emails`, `contact_phones`, `contact_urls`
- **Run info**: `search_query`, `scraped_at`

Contact data is sparse on ge.ch. `contact_name` and `contact_title` are read from the Postuler block or from an Observations line such as "a l'attention de Madame ...". In a full crawl of the portal only about 1 offer in 50 names a person, and no offer showed an email address or phone number. Email and phone fields are still extracted whenever they appear in the offer text, but expect them to be empty. Most offers apply through the SIRH or devenez.ge.ch link in `apply_url`.

`updated_at` and `updated_at_text` are always null. ge.ch prints the current date as "Mise a jour" on every page, which is not a per-job edit date, so the actor does not report it. Use `posted_at_datetime` for freshness.

`remote_option` is `hybrid` when the offer text mentions teleworking and null otherwise. ge.ch has no remote field.

Contract type: ge.ch sometimes shows CDI in the header data and a fixed term such as "CDD jusqu'au 30 juin 2027" in the page body. The actor prefers the body value: `contract_type` is the bare type (`CDD`) and `contract_type_note` keeps the full wording. With `includeJobDetails` off, `contract_type` is filled from your `contractType` filter when the job page is not read.

`search_query` is the keyword that matched, the listing URL for `searchUrls`, `directUrls` for single job URLs, and null for a keyword-less browse.

Fields marked detail mode in the dataset schema are empty when `includeJobDetails` is off.

### Sample output

```json
{
  "id": "1161",
  "reference_number": "114652",
  "title": "Ingénieure ou ingénieur ITI SAED ECM",
  "job_url": "https://www.ge.ch/offres-emploi-etat-geneve/liste-offres/1161",
  "company_name": "Département des institutions et du numérique",
  "service": "Office cantonal des systèmes d'information et du numérique",
  "location": "Rue du Grand-Pré 64-66",
  "posted_at_datetime": "2026-09-24T00:00:00Z",
  "posted_at_text": "24 septembre 2026",
  "application_deadline": "2026-10-17T00:00:00Z",
  "contract_type": "CDI",
  "employment_type": "full-time",
  "work_percentage_text": "100%",
  "salary_class": "classe 19",
  "apply_url": "https://ge.ch/sirhrecrutement/recrutement/candidatAction.do?NOREFPOSTE=114652",
  "apply_platform": "ge.ch-sirh"
}
```

### Tips

- The portal lists about 50 to 80 offers depending on the season. To get everything, run with no keyword and `maxResults` set to 200.
- Salary class is a Swiss public-sector grid, from classe 4 to classe 33. Every row links the official scale.
- Some offers, such as police recruitment, apply through devenez.ge.ch. Those rows have no `apply_url`, and `apply_instructions` explains how to apply.
- "Dès que possible" and "A convenir" start dates stay as text in `start_date_text`. `job_start_date` is set only when the page gives a real date.
- Keyword matching also looks at the mission and profile text, ranked after title and service hits. Use a more specific term, or `activityDomain`, if you want tighter matches. Single job URLs that no longer exist are skipped with a warning.
- Even with `includeJobDetails` off, the actor reads job pages to order by date and to match keywords. You are billed only for what the run returns.

### Use with AI Agents (MCP)

Connect this actor to any MCP-compatible AI client: Claude Desktop, Claude.ai, Cursor, VS Code, LangChain, LlamaIndex, or custom agents.

Configure the MCP server with this actor preconfigured at `mcp.apify.com?tools=santamaria-automations/ge-ch-emploi-scraper`.

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=santamaria-automations/ge-ch-emploi-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}
```

For per-tool schemas and copy-paste snippets for MCP clients, see the [MCP tab](https://console.apify.com/actors/lB02sVFUwZniza6Cr/info/api/mcp?build=latest).

See the [auto-generated API tab](https://console.apify.com/actors/lB02sVFUwZniza6Cr/info/api) for language-specific examples (cURL, JS, Python, .NET, Ruby, PHP).

### Related actors

**Cross-site job extraction**

- [Website Job Extractor](https://apify.com/santamaria-automations/website-job-extractor): pull jobs from any career page
- [Indeed HTTP Scraper](https://apify.com/santamaria-automations/indeed-http-scraper): Indeed listings worldwide, including Switzerland
- [Glassdoor Scraper](https://apify.com/santamaria-automations/glassdoor-scraper): jobs plus company ratings and reviews

**Swiss job boards**

- [Jobs.ch Scraper](https://apify.com/santamaria-automations/jobs-ch-scraper): Switzerland's largest job board
- [Jobup.ch Scraper](https://apify.com/santamaria-automations/jobup-ch-scraper): Romandie, the French-speaking part of Switzerland
- [Arbeit.swiss Scraper](https://apify.com/santamaria-automations/arbeit-swiss-scraper): the federal job-room platform

### Support

- **Questions or issues?** Email contact@nanoscrape.com, we typically reply within 6 hours. You can also open a ticket in the [Issues tab](https://console.apify.com/actors/lB02sVFUwZniza6Cr/info/issues) on the actor page.
- **Feature request for this actor?** Open a ticket in the [Issues tab](https://console.apify.com/actors/lB02sVFUwZniza6Cr/info/issues) or email contact@nanoscrape.com with the field you need and, if possible, a ge.ch URL that shows it live.
- **Need a scraper for a job board we don't cover yet?** Email contact@nanoscrape.com with the target site and your rough usage volume. We ship one-off boards regularly.

# Actor input Schema

## `searchQueries` (type: `array`):

French keywords, one query each (for example 'informatique', 'comptable', 'enseignant'). ge.ch has no server-side keyword search, so each keyword is matched here, ignoring case and accents, against the title, service, department and the job's own mission and profile text (generic lines such as 'maitrise des outils informatiques' are ignored). Title and service matches come first. Leave empty to return all open offers.

## `maxResultsPerQuery` (type: `integer`):

Maximum jobs returned for each keyword or search URL. When left empty it equals Max total results if that is set, otherwise 25.

## `maxResults` (type: `integer`):

Total cap across all queries. Leave empty to use max results per query multiplied by the number of queries (plus direct URLs). The whole portal lists about 50 to 80 open offers depending on the season.

## `sortBy` (type: `string`):

ge.ch has no server-side sort. Newest and oldest order jobs by publication date, read from each job page, so the newest jobs are picked even when a cap applies. Portal order keeps the site's own order and is fastest.

## `includeJobDetails` (type: `boolean`):

Return the full description (text, HTML, Markdown), profile, benefits, workplace, apply link and reference number from each job page. When off, rows carry the listing fields only (title, department, service, workload, salary class) plus the publication date.

## `contractType` (type: `string`):

Filter by contract type. Applied by ge.ch itself.

## `department` (type: `string`):

Only offers from this department. Applied by ge.ch itself.

## `minFunctionClass` (type: `integer`):

Only offers at or above this salary class (classe de fonction minimum, 4 to 33). Applied by ge.ch itself. Leave empty for all classes.

## `activityDomain` (type: `string`):

Only offers in this domain of activity (the ge.ch 'Domaine d'activite' filter). Applied by ge.ch itself.

## `maxWorkPercentage` (type: `string`):

Only offers whose workload (taux d'activite) starts at or below this percentage, for example 50 finds part-time roles. Applied by ge.ch itself.

## `postedWithinDays` (type: `integer`):

Only offers published in the last N days. Applied by this actor on each offer's publication date, so it needs to read the job pages. Leave empty for all offers.

## `searchUrls` (type: `array`):

ge.ch listing URLs to read, for example https://www.ge.ch/offres-emploi-etat-geneve/liste-offres?type\_contrat=CDI. Each URL counts as one query.

## `directUrls` (type: `array`):

Individual ge.ch job page URLs, for example https://www.ge.ch/offres-emploi-etat-geneve/liste-offres/2150. Always read as full detail pages, useful to check whether a posting is still live.

## `maxConcurrency` (type: `integer`):

Job pages fetched in parallel. Higher is faster. The default of 5 is safe for ge.ch.

## Actor input object example

```json
{
  "searchQueries": [
    "informatique"
  ],
  "sortBy": "newest",
  "includeJobDetails": true,
  "contractType": "any",
  "department": "any",
  "activityDomain": "any",
  "maxWorkPercentage": "any",
  "maxConcurrency": 5
}
```

# Actor output Schema

## `jobListings` (type: `string`):

Dataset with one row per job offer.

# 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 = {
    "searchQueries": [
        "informatique"
    ],
    "maxConcurrency": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("santamaria-automations/ge-ch-emploi-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 = {
    "searchQueries": ["informatique"],
    "maxConcurrency": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("santamaria-automations/ge-ch-emploi-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 '{
  "searchQueries": [
    "informatique"
  ],
  "maxConcurrency": 5
}' |
apify call santamaria-automations/ge-ch-emploi-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,santamaria-automations/ge-ch-emploi-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/lB02sVFUwZniza6Cr/builds/9gwgnL5f3oFSDhqXX/openapi.json
