# Vd.ch Etat de Vaud Scraper: Vaud Canton Government Jobs (`santamaria-automations/vd-ch-emploi-scraper`) Actor

Scrape Etat de Vaud (Vaud canton, Switzerland) public-sector jobs from offres-emploi.vd.ch. Returns title, category, department, French description, salary class, contract, activity rate, address, and posting dates. Oracle Recruiting API, no login.

- **URL**: https://apify.com/santamaria-automations/vd-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

Pay per event

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

## Vd.ch Etat de Vaud Scraper: Vaud Canton Public-Sector Jobs

Extract public-sector job listings from [offres-emploi.vd.ch](https://offres-emploi.vd.ch), the official recruitment portal of the [Etat de Vaud](https://www.vd.ch/etat-droit-finances/etat-employeur/offres-demploi) (Vaud canton, French Switzerland). Covers all departments of the cantonal administration, judicial system, police, and public services. Typical inventory is about 40-100 open positions at any time.

No login required. Fast and light: a full run of the portal finishes in well under a minute.

### What it does

Pulls live job listings from the Vaud canton (Etat de Vaud) government careers site. The employer is always Etat de Vaud (public administration), so instead of company diversity the actor surfaces the fields that matter for public-sector jobs: cantonal salary class (Classe salariale 1..18+), contract type (CDI Droit public, CDD Droit public, CDD Auxiliaires mensualises, Apprentissage), activity rate (taux d'activite) as a percent range with parsed min/max, work address, department (parsed from address), and category (Secteur administratif, Intervention sociale, Communication, Finance et audit, etc.).

Full French job descriptions are available in HTML, plain text, and Markdown. Missions (ExternalResponsibilitiesStr) and Profil (ExternalQualificationsStr) blocks are also exposed separately for structured consumption.

By default (`sortBy="newest"`), results are ordered by posting date descending, ideal for scheduled scrapes. Other options: `oldest`, `relevance`, `title` (alphabetical A-Z).

Use `startUrls` for direct job page URLs (e.g., still-alive re-checks). Use `searchQueries` to filter by keyword. Leave both empty to scrape the whole portal (about 40-100 jobs). An empty input `{}` returns a small demo of 5 jobs.

Filter by:

- **Keywords**: French job title terms (`infirmier`, `comptable`, `juriste`)
- **Category**: client-side filter on the Etat de Vaud job category (requires `includeJobDetails=true`)
- **Start URLs**: paste specific offres-emploi.vd.ch job detail URLs

### Sample output

```json
{
  "_type": "job",
  "id": "5802",
  "requisition_id": "300000150109973",
  "title": "Gestionnaire de dossiers - domaine judiciaire",
  "short_description": "Ordre Judiciaire Vaudois - Tribunal des mineurs - 80-100%",
  "job_url": "https://offres-emploi.vd.ch/#fr/sites/CX_1/job/5802/",
  "source_url": "https://offres-emploi.vd.ch/#fr/sites/CX_1/job/5802/",
  "source_platform": "vd.ch/emploi",
  "apply_url": "https://offres-emploi.vd.ch/#fr/sites/CX_1/job/5802/apply/email/",
  "company_name": "Etat de Vaud",
  "department": "Tribunal des mineurs",
  "location": "Lausanne, Suisse",
  "primary_city": "Lausanne",
  "country": "CH",
  "region": "Vaud",
  "latitude": null,
  "longitude": null,
  "secondary_locations": [],
  "work_address": "Tribunal des mineurs\nChemin du Trabandan 28\n1014 Lausanne",
  "posted_at_text": "2026-09-03",
  "posted_at_datetime": "2026-09-03T05:00:00Z",
  "expires_at": "2026-09-16T21:55:00Z",
  "start_date_text": "De suite ou a convenir. CDD jusqu'au 31 decembre 2026, eventuellement renouvelable",
  "employment_type": "CDD Auxiliaires mensualises",
  "activity_rate": "80-100%",
  "activity_rate_min": 80,
  "activity_rate_max": 100,
  "salary_class": "6",
  "category": "Secteur administratif",
  "requisition_type": "Professionnels",
  "workplace_type": null,
  "description": "Le domaine judiciaire vous interesse ? Effectuer des taches administratives variees et essentielles au bon fonctionnement de la justice vous motive ?...",
  "description_full": "Le domaine judiciaire vous interesse ?\n\nEffectuer des taches administratives variees...",
  "description_html": "<p>Le domaine judiciaire vous interesse&nbsp;?</p><h3>Missions</h3><ul>...",
  "description_md": "Le domaine judiciaire vous interesse ?\n\n## Missions\n\n- Traiter le courrier du greffe...",
  "responsibilities_html": "<ul><li>Traiter le courrier du greffe et preparer diverses correspondances.</li>...</ul>",
  "qualifications_html": "<ul><li>CFC d'employe-e de commerce, ou tout titre juge equivalent</li>...</ul>",
  "corporate_description_html": "<p>L'Etat de Vaud propose une grande diversite d'emplois...</p>",
  "organization_description_html": "<div>Aux cotes des pouvoirs legislatif et executif, l'Ordre judiciaire vaudois (OJV) assure le role de troisieme pouvoir cantonal.</div>",
  "contact_emails": [],
  "contact_phones": [],
  "contact_urls": ["https://www.vd.ch/dfts/dgf"],
  "contact_name": "Natalia Bérard",
  "contact_title": "Conservatrice du registre foncier",
  "job_description_document_url": "https://partage.vd.ch/u/hKiE_HcohOOWqITr/d7fd1c62-216b-4574-968f-24ad16db14a0?l",
  "language": "fr",
  "search_query": null,
  "scraped_at": "2026-09-03T22:00:00Z"
}
```

### Pricing

Pay per result. A start charge of $0.001 applies per run (`apify-actor-start`). Then you are charged per job delivered: `job-serp-result` at $1.00 per 1,000 jobs without full details, and `job-detail-result` at $2.00 per 1,000 jobs with full details (the default). Set a maximum cost per run and the actor stops cleanly at that limit and reports how many jobs it delivered.

**New to Apify?** Every account gets a $5 free monthly platform credit.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `searchQueries` | array of strings | - | Optional French keywords to filter listings. Leave empty to scrape all jobs (typical: about 40-100). Each entry runs as a separate search. |
| `startUrls` | array of strings | - | Direct offres-emploi.vd.ch job detail URLs. Use for still-alive rechecks or targeted scrapes. |
| `category` | string | - | Optional Etat de Vaud category to filter client-side. Requires `includeJobDetails=true` (the category is part of the full job details). Examples: "Secteur administratif", "Intervention sociale", "Communication". |
| `sortBy` | enum | `"newest"` | Result ordering. `"newest"` (recommended for schedules), `"oldest"`, `"relevance"` or `"title"` (A-Z). |
| `includeJobDetails` | boolean | true | Add the full job details for each listing: description, category, salary_class, employment_type, activity_rate, work_address, posted_at_datetime, expires_at. Recommended for most use cases. |
| `maxResults` | integer | 100 | Total cap across all queries (max 500). Portal typically has about 40-100 active jobs. When the whole input is empty, 5. |
| `maxResultsPerQuery` | integer | 100 | Cap per individual keyword. |
| `maxConcurrency` | integer | 5 | Parallel detail-page fetches (max 10). |

### Output fields

**Identity**

- `id`: numeric job id on the portal (short, human-shareable)
- `requisition_id`: internal requisition number (long numeric)
- `title`: position title in French
- `short_description`: sub-headline from the SERP card
- `job_url`, `source_url`: canonical offres-emploi.vd.ch URL (hash-routed)
- `apply_url`: the "Postuler maintenant" URL for the position

**Employer**

- `company_name`: always "Etat de Vaud" (single-employer portal)
- `department`: department/service parsed from work address (e.g., "Ordre Judiciaire Vaudois", "Police cantonale vaudoise")

**Location**

- `location`: primary location display string (city + country)
- `primary_city`: parsed primary city (e.g., "Lausanne")
- `country`: always "CH"
- `region`: always "Vaud"
- `latitude`, `longitude`: coordinates when the portal publishes them (some jobs return blank)
- `secondary_locations`: additional work cities (rare on Vaud canton)
- `work_address`: full multi-line postal address (department + street + postal code)

**Contract and schedule (requires includeJobDetails=true)**

- `employment_type`: "CDI Droit public", "CDD Droit public", "CDD Auxiliaires mensualises", "Apprentissage", etc.
- `activity_rate`: taux d'activite as written (e.g. "80-100%", "50%")
- `activity_rate_min`, `activity_rate_max`: parsed integer percent bounds (80, 100)
- `salary_class`: Etat de Vaud Classe salariale (integer 1..18+ under the cantonal salary grid; not a numeric salary)
- `start_date_text`: free-form start date as written by the employer

**Category and type (requires includeJobDetails=true)**

- `category`: Etat de Vaud category (Secteur administratif, Intervention sociale, Communication, Finance et audit, Projet-recherche-statistique, etc.)
- `requisition_type`: "Professionnels", "Apprentissage", "Stage"
- `workplace_type`: Onsite/Hybrid/Remote when tagged (rare for public sector)

**Dates**

- `posted_at_text`: ISO date posted (YYYY-MM-DD)
- `posted_at_datetime`: ISO 8601 UTC datetime (ExternalPostedStartDate)
- `expires_at`: ISO 8601 UTC posting end date (ExternalPostedEndDate)

**Description (requires includeJobDetails=true)**

- `description`: ~500 character plain-text snippet
- `description_full`: full plain text with line breaks preserved
- `description_html`: composite HTML (intro + Missions + Profil + Organisation)
- `description_md`: Markdown rendering
- `responsibilities_html`: HTML block for Missions
- `qualifications_html`: HTML block for Profil / competences
- `corporate_description_html`: about Etat de Vaud as employer
- `organization_description_html`: about the specific hiring service/office

**Contacts (extracted from description + address)**

- `contact_emails`: email addresses found (text and mailto links)
- `contact_phones`: Swiss phone numbers found
- `contact_urls`: external links found in the description sections (for example the department site)
- `contact_name`, `contact_title`: the contact person named in the posting and their function; null when no single person is clearly named
- `job_description_document_url`: link to the job description document (cahier des charges) when the posting has one; null otherwise

**Meta**

- `language`: content language code (usually "F" or "fr")
- `search_query`: keyword that produced this row (null when unfiltered)
- `scraped_at`: RFC 3339 UTC scrape timestamp

### Common use cases

**Daily monitoring of new Etat de Vaud jobs (recommended)**

Run on a schedule with `sortBy="newest"` (default). Newest postings appear first so incremental scrapes always pick them up.

```json
{
  "sortBy": "newest",
  "maxResults": 100,
  "includeJobDetails": true
}
```

**Full inventory snapshot (all active jobs)**

The portal typically has about 40-100 open positions. A single unfiltered run captures the whole market:

```json
{
  "sortBy": "newest",
  "maxResults": 200,
  "includeJobDetails": true
}
```

**Targeted role search**

```json
{
  "searchQueries": ["infirmier", "comptable"],
  "sortBy": "newest",
  "maxResults": 100,
  "includeJobDetails": true
}
```

**Still-alive re-check of tracked postings**

```json
{
  "startUrls": [
    "https://offres-emploi.vd.ch/#fr/sites/CX_1/job/5802/",
    "https://offres-emploi.vd.ch/#fr/sites/CX_1/job/5817/"
  ],
  "includeJobDetails": true
}
```

### Related actors

**Swiss regional peers**

- [Jobs.ch Scraper](https://apify.com/santamaria-automations/jobs-ch-scraper) - a Swiss general job board
- [Jobscout24 Scraper](https://apify.com/santamaria-automations/jobscout24-ch-scraper) - Swiss regional jobs
- [Jobup.ch Scraper](https://apify.com/santamaria-automations/jobup-ch-scraper) - French Switzerland general jobs
- [ICTjobs.ch Scraper](https://apify.com/santamaria-automations/ictjobs-ch-scraper) - Swiss IT jobs
- [Sozjobs.ch Scraper](https://apify.com/santamaria-automations/sozjobs-ch-scraper) - Swiss social sector jobs

**Job aggregation**

- [Job Feed](https://apify.com/santamaria-automations/job-feed) - Unified job feed combining multiple European and global sources

**Career site scrapers**

- [Career Site Jobs Scraper](https://apify.com/santamaria-automations/career-site-jobs-scraper) - Extract jobs from any company career page
- [Enterprise ATS Jobs Scraper](https://apify.com/santamaria-automations/enterprise-ats-jobs-scraper) - Multi-vendor ATS (SuccessFactors, Taleo, Workday, Oracle Recruiting)

### Questions and support

Contact us at <contact@nanoscrape.com> or use the actor's Issues tab on Apify.

# Actor input Schema

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

Optional keywords to filter listings. Leave empty to scrape all jobs. The Vaud canton portal (offres-emploi.vd.ch) exposes about 40-100 jobs in total, so most users leave this empty. Example keywords: 'infirmier', 'comptable', 'juriste'.

## `startUrls` (type: `array`):

Direct offres-emploi.vd.ch job detail URLs to scrape (skips search). Example: https://offres-emploi.vd.ch/#fr/sites/CX_1/job/5802/

## `category` (type: `string`):

Optional Etat de Vaud job category to filter (client-side match on Category field). Examples: 'Secteur administratif', 'Intervention sociale', 'Communication', 'Finance et audit', 'Projet, recherche et statistique'. Leave empty for all categories.

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

How to order results. 'newest' fetches jobs date-descending (recommended for scheduled scrapes). 'oldest' returns date-ascending. 'relevance' uses the site's default relevance ordering. 'title' sorts alphabetically A-Z.

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

When enabled, fetches the full job detail page for each listing to populate description_full, description_html, description_md, category, salary_class, contract_type, activity_rate, and work_address fields. When disabled, only the listing-level fields are returned (title, ID, primary_location, posted_at). Charged as job-detail-result when enabled, job-serp-result when disabled.

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

Total cap across all queries. The Etat de Vaud portal typically has 40-100 active jobs. Leave the whole input empty for a small demo of 5 jobs; with any keyword, URL or filter set and this field empty, the cap is 100.

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

Maximum results per individual search keyword (100 when empty).

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

Maximum number of parallel detail-page fetches (when includeJobDetails=true).

## Actor input object example

```json
{
  "searchQueries": [],
  "category": "",
  "sortBy": "newest",
  "includeJobDetails": true,
  "maxResults": 100,
  "maxResultsPerQuery": 100,
  "maxConcurrency": 5
}
```

# Actor output Schema

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

Dataset containing Etat de Vaud (Vaud canton, Switzerland) public-sector job listings scraped from offres-emploi.vd.ch. Each row includes title, category, department, French description (HTML+text+markdown), salary_class, contract_type, activity_rate (taux d'activite), work_address, apply_url, primary_location, secondary_locations, posted_at_datetime, and expires_at.

# 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": [],
    "sortBy": "newest",
    "includeJobDetails": true,
    "maxResults": 100,
    "maxResultsPerQuery": 100,
    "maxConcurrency": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("santamaria-automations/vd-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": [],
    "sortBy": "newest",
    "includeJobDetails": True,
    "maxResults": 100,
    "maxResultsPerQuery": 100,
    "maxConcurrency": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("santamaria-automations/vd-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": [],
  "sortBy": "newest",
  "includeJobDetails": true,
  "maxResults": 100,
  "maxResultsPerQuery": 100,
  "maxConcurrency": 5
}' |
apify call santamaria-automations/vd-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/vd-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/VqDHxpArpZM5jh58f/builds/pI0pzVYpkT8uEVeA8/openapi.json
