{
  "data": {
    "id": "9evTfDGOOXMYXba9u",
    "actId": "s0z4uLgc6jZVLtD28",
    "userId": "NL4ThgItAwaoBlffQ",
    "startedAt": "2026-11-02T16:38:30.336Z",
    "finishedAt": "2026-11-02T16:38:35.934Z",
    "status": "SUCCEEDED",
    "meta": {
      "origin": "CLI"
    },
    "stats": {
      "durationMillis": 5500,
      "runTimeSecs": 5.5,
      "computeUnits": 0.006111111111111111,
      "imageSizeBytes": 318988288
    },
    "options": {
      "useCache": true,
      "betaPackages": false,
      "memoryMbytes": 4096,
      "diskMbytes": 8192
    },
    "createdByOrganizationMemberUserId": "zifX0mkAFMHe8CatY",
    "exitCode": 0,
    "inputSchema": "{\n  \"title\": \"jobs.ch Scraper Input\",\n  \"description\": \"Search and browse jobs.ch job listings, resolve companies, or paste jobs.ch search and detail URLs.\",\n  \"type\": \"object\",\n  \"schemaVersion\": 1,\n  \"properties\": {\n    \"mode\": {\n      \"title\": \"Mode\",\n      \"type\": \"string\",\n      \"description\": \"What to fetch. `search` runs classic keyword search (or semantic with `searchMode=semantic`). `browse` uses filters only. `byCompany` resolves company names/IDs. `byUrls` accepts jobs.ch search or job-detail URLs. The `byRegion`/`byIndustry`/`byEmploymentType`/`byCategory` modes are kept for backward compatibility and require their matching filter.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"search\",\n        \"browse\",\n        \"byCompany\",\n        \"byUrls\",\n        \"byRegion\",\n        \"byIndustry\",\n        \"byEmploymentType\",\n        \"byCategory\"\n      ],\n      \"enumTitles\": [\n        \"Keyword search\",\n        \"Browse all jobs by filters\",\n        \"Jobs at specific companies\",\n        \"Search or job URLs\",\n        \"Browse by canton / region\",\n        \"Browse by industry\",\n        \"Browse by employment type\",\n        \"Browse by occupational field\"\n      ],\n      \"default\": \"search\",\n      \"prefill\": \"search\"\n    },\n    \"searchMode\": {\n      \"title\": \"Search relevance\",\n      \"type\": \"string\",\n      \"description\": \"`classic` uses jobs.ch's exact keyword API with all filters and sort. `semantic` uses the site's fuzzy, cross-language search page (filters still apply; sort is relevance-only).\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"classic\",\n        \"semantic\"\n      ],\n      \"enumTitles\": [\n        \"Classic (exact keyword)\",\n        \"Semantic (fuzzy, cross-language)\"\n      ],\n      \"default\": \"classic\",\n      \"prefill\": \"classic\"\n    },\n    \"searchQueries\": {\n      \"title\": \"Search queries\",\n      \"type\": \"array\",\n      \"description\": \"One or more free-text keywords (mode=search). Multiple queries are run in order and deduplicated by job ID. Leave empty to browse all jobs matching the filters.\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"default\": [],\n      \"prefill\": [\n        \"software engineer\"\n      ]\n    },\n    \"companyNames\": {\n      \"title\": \"Company names\",\n      \"type\": \"array\",\n      \"description\": \"Company names to resolve against jobs.ch's company directory (mode=byCompany). Exact names are preferred; unmatched names fall back to a keyword search filtered by company name.\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"default\": [],\n      \"prefill\": []\n    },\n    \"companyIds\": {\n      \"title\": \"Company IDs\",\n      \"type\": \"array\",\n      \"description\": \"jobs.ch company IDs (mode=byCompany). IDs are the most reliable company filter and take priority over names.\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"default\": [],\n      \"prefill\": []\n    },\n    \"startUrls\": {\n      \"title\": \"Start URLs\",\n      \"type\": \"array\",\n      \"description\": \"jobs.ch search URLs or job-detail URLs (mode=byUrls). Search URLs carry their own filters; invalid or non-jobs.ch URLs are skipped.\",\n      \"editor\": \"requestListSources\",\n      \"items\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"url\": {\n            \"type\": \"string\",\n            \"title\": \"URL\",\n            \"description\": \"jobs.ch search or job-detail URL.\"\n          }\n        },\n        \"required\": [\n          \"url\"\n        ]\n      },\n      \"default\": [],\n      \"prefill\": []\n    },\n    \"regions\": {\n      \"title\": \"Cantons / regions\",\n      \"type\": \"array\",\n      \"description\": \"Filter by one or more Swiss canton/language-regions (multi-value OR). Allowed IDs: 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25. Required for mode=byRegion; optional elsewhere.\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"default\": [],\n      \"prefill\": []\n    },\n    \"industries\": {\n      \"title\": \"Industries\",\n      \"type\": \"array\",\n      \"description\": \"Filter by one or more employer industries (multi-value OR). Allowed IDs: 1-24. Required for mode=byIndustry; optional elsewhere.\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"default\": [],\n      \"prefill\": []\n    },\n    \"categories\": {\n      \"title\": \"Occupational fields\",\n      \"type\": \"array\",\n      \"description\": \"Filter by one or more occupational fields / job functions (multi-value OR). Allowed IDs: 97-261 (165 jobs.ch categories; labels in README). Required for mode=byCategory.\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"default\": [],\n      \"prefill\": []\n    },\n    \"employmentTypes\": {\n      \"title\": \"Employment types\",\n      \"type\": \"array\",\n      \"description\": \"Filter by one or more contract types (multi-value OR). Allowed IDs: 1 Temporary, 2 Freelance, 3 Internship, 4 Supplementary income, 5 Permanent position, 6 Apprenticeship. Required for mode=byEmploymentType.\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"default\": [],\n      \"prefill\": []\n    },\n    \"positions\": {\n      \"title\": \"Seniority levels\",\n      \"type\": \"array\",\n      \"description\": \"Filter by one or more seniority levels (multi-value OR). Allowed IDs: 1 Executive position, 2 Specialist, 3 Employee.\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"default\": [],\n      \"prefill\": []\n    },\n    \"languageSkills\": {\n      \"title\": \"Required languages\",\n      \"type\": \"array\",\n      \"description\": \"Filter by one or more required working languages (multi-value OR). Allowed values: de, en, fr, it.\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"default\": [],\n      \"prefill\": []\n    },\n    \"companySegments\": {\n      \"title\": \"Company size\",\n      \"type\": \"array\",\n      \"description\": \"Filter by employer segment (multi-value OR). Allowed values: gu (large company), kmu (SME), pdl (personnel-service provider).\",\n      \"editor\": \"stringList\",\n      \"items\": {\n        \"type\": \"string\"\n      },\n      \"default\": [],\n      \"prefill\": []\n    },\n    \"location\": {\n      \"title\": \"City / place\",\n      \"type\": \"string\",\n      \"description\": \"Optional free-text city/place refinement (e.g. Zürich, Geneva, Basel).\",\n      \"editor\": \"textfield\",\n      \"default\": \"\",\n      \"prefill\": \"\"\n    },\n    \"employmentGradeMin\": {\n      \"title\": \"Workload % — minimum\",\n      \"type\": \"integer\",\n      \"description\": \"Lower bound of the workload range in percent.\",\n      \"minimum\": 0,\n      \"maximum\": 100,\n      \"default\": 0,\n      \"prefill\": 0\n    },\n    \"employmentGradeMax\": {\n      \"title\": \"Workload % — maximum\",\n      \"type\": \"integer\",\n      \"description\": \"Upper bound of the workload range in percent.\",\n      \"minimum\": 0,\n      \"maximum\": 100,\n      \"default\": 100,\n      \"prefill\": 100\n    },\n    \"publishedSince\": {\n      \"title\": \"Published since\",\n      \"type\": \"string\",\n      \"description\": \"Only jobs first published within this time window.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"\",\n        \"1\",\n        \"3\",\n        \"7\",\n        \"30\"\n      ],\n      \"enumTitles\": [\n        \"(Any time)\",\n        \"Last 24 hours\",\n        \"Last 3 days\",\n        \"Last 7 days\",\n        \"Last 30 days\"\n      ],\n      \"default\": \"\",\n      \"prefill\": \"\"\n    },\n    \"sortBy\": {\n      \"title\": \"Sort order\",\n      \"type\": \"string\",\n      \"description\": \"Classic search sort order: relevance or newest-first. Semantic search always uses relevance.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"relevance\",\n        \"date\"\n      ],\n      \"enumTitles\": [\n        \"Relevance\",\n        \"Newest first\"\n      ],\n      \"default\": \"relevance\",\n      \"prefill\": \"relevance\"\n    },\n    \"includeDetails\": {\n      \"title\": \"Include job details & company profile\",\n      \"type\": \"boolean\",\n      \"description\": \"Fetch each job's detail page for full description, apply URL/method, salary estimate, contacts, languages, categories, regions, deadline, benefits and company profile/ratings. Adds one request per job plus one cached request per company.\",\n      \"default\": false,\n      \"prefill\": false\n    },\n    \"descriptionFormat\": {\n      \"title\": \"Description format\",\n      \"type\": \"string\",\n      \"description\": \"Format of the enriched description: plain text, original HTML, or both.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"text\",\n        \"html\",\n        \"both\"\n      ],\n      \"enumTitles\": [\n        \"Plain text\",\n        \"HTML\",\n        \"Both\"\n      ],\n      \"default\": \"text\",\n      \"prefill\": \"text\"\n    },\n    \"maxDescriptionLength\": {\n      \"title\": \"Max description length\",\n      \"type\": \"integer\",\n      \"description\": \"Truncate descriptions to this many characters (0 = no truncation).\",\n      \"minimum\": 0,\n      \"maximum\": 50000,\n      \"default\": 0,\n      \"prefill\": 0\n    },\n    \"maxItems\": {\n      \"title\": \"Max items\",\n      \"type\": \"integer\",\n      \"description\": \"Maximum number of jobs to return. jobs.ch serves 20 rows per page and caps each search at 2,000 rows.\",\n      \"minimum\": 1,\n      \"maximum\": 2000,\n      \"default\": 20,\n      \"prefill\": 3\n    },\n    \"maxPages\": {\n      \"title\": \"Max pages\",\n      \"type\": \"integer\",\n      \"description\": \"Maximum result pages to request per query/filter combination (jobs.ch caps searches at 100 pages).\",\n      \"minimum\": 1,\n      \"maximum\": 100,\n      \"default\": 100,\n      \"prefill\": 1\n    },\n    \"locale\": {\n      \"title\": \"Language\",\n      \"type\": \"string\",\n      \"description\": \"jobs.ch site language for URLs and translated labels.\",\n      \"editor\": \"select\",\n      \"enum\": [\n        \"en\",\n        \"de\",\n        \"fr\"\n      ],\n      \"enumTitles\": [\n        \"English\",\n        \"German (Deutsch)\",\n        \"French (Français)\"\n      ],\n      \"default\": \"en\",\n      \"prefill\": \"en\"\n    },\n    \"incrementalMode\": {\n      \"title\": \"Incremental mode\",\n      \"type\": \"boolean\",\n      \"description\": \"When enabled, only new or changed jobs are pushed. State is stored under `stateKey` in the key-value store.\",\n      \"default\": false,\n      \"prefill\": false\n    },\n    \"stateKey\": {\n      \"title\": \"Incremental state key\",\n      \"type\": \"string\",\n      \"description\": \"Key-value store key for incremental state (default `jobs_ch_incremental_state`). Only used when `incrementalMode` is true.\",\n      \"editor\": \"textfield\",\n      \"default\": \"\",\n      \"prefill\": \"\"\n    }\n  },\n  \"required\": [\n    \"mode\"\n  ]\n}",
    "readme": "# Jobs.ch Swiss Job Listings by Region & Industry\n\nScrape **jobs.ch**, Switzerland's largest job board, through its public search and company APIs. Run classic keyword searches, browse by filters, resolve employers by name or company ID, or paste jobs.ch search/job URLs. Listings can be enriched with the full description, apply URL and method, jobs.ch salary estimate, contacts, language requirements, occupational categories, regions, locations, application deadline, benefits and employer profile/ratings.\n\nNo login, cookies, API key or proxy is needed.\n\n## Modes\n\n| Mode | What it does |\n|---|---|\n| `search` | Classic keyword search across one or more queries. With `searchMode: \"semantic\"` it uses jobs.ch's fuzzy, cross-language search page instead. |\n| `browse` | Filter-only browsing with no keyword. |\n| `byCompany` | Resolves company names through jobs.ch's company directory and searches all openings. Company IDs are also accepted and are the most reliable option. |\n| `byUrls` | Accepts jobs.ch search URLs (their filters are preserved) and job-detail URLs. Detail URLs report `jobStatus`: `online`, `offline`, `expired` or `unknown`. |\n| `byRegion` | Legacy mode: requires at least one value in `regions`; filters only. |\n| `byIndustry` | Legacy mode: requires at least one value in `industries`; filters only. |\n| `byEmploymentType` | Legacy mode: requires at least one value in `employmentTypes`; filters only. |\n| `byCategory` | Legacy mode: requires at least one value in `categories`; filters only. |\n\n## Filters (all multi-value)\n\n- `regions` — 24 Swiss canton/language-region values.\n- `industries` — 24 employer-industry values.\n- `categories` — all **165** jobs.ch occupational categories (job function, distinct from employer industry).\n- `employmentTypes` — temporary, freelance, internship, supplementary income, permanent, apprenticeship.\n- `positions` — executive, specialist, employee.\n- `languageSkills` — German, English, French, Italian.\n- `companySegments` — large company, SME, personnel-service provider.\n- `location` — free-text city/place.\n- `employmentGradeMin` / `employmentGradeMax` — workload range in percent.\n- `publishedSince` — last 1, 3, 7 or 30 days.\n- `sortBy` — relevance or newest-first (classic search).\n\nMultiple values in one filter are combined with OR; different filters are combined with AND. Jobs.ch caps every search at **2,000 rows / 100 pages of 20**. Use narrower filters, date windows or several queries to get past the cap.\n\n## Output fields\n\nEvery record includes the job identity and listing fields:\n\n- `jobId`, `title`, `searchQuery` (when a keyword was used), `jobStatus` (URL mode only)\n- `companyId`, `companyName`, `companySlug`, `companyUrl`, `companyLogoUrl`\n- `location`, `locationDetails[]` (street, city, postal-code, canton, country, latitude, longitude)\n- `workloadMin`, `workloadMax`, `workloadText`\n- `contractType`, `contractTypes[]`, `seniorityLevel`\n- `tags[]`, `benefitTags[]`, `easyApply`, `quickApply`, `homeOfficeOffered`\n- `postedDate`, `initialPostedDate`, `postedRelative`, `initialPostedRelative`\n- `isNew`, `isBoosted`, `isPaid`, `isActive`, `headhunterApplicationAllowed`\n- `jobUrl`, `recordType`, `scrapedAt`\n\nWith `includeDetails: true`:\n\n- `description` (plain text), `descriptionHtml` (original HTML), `descriptionSnippet`\n- `applyUrl`, `applyMethod` (`external` or `form`), `applicationDeadline`\n- `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `salaryIsEstimate`\n- `contacts[]` (`name`, `firstName`, `lastName`, `role`, `phone`), plus `contactName` / `contactPhone`\n- `languageSkills[]` (`language`, `level`), `skills[]`, `benefits[]`\n- `category` (full taxonomy path), `categories[]`, `regions[]`, `cantonCode`\n- `roleSummary`, `industry`, `workHoursText`, `totalOpenings`, `startDate`, `jobImmediateStart`\n- `companyWebsite`, `companyOverview`, `companyDescription`\n- `companySize`, `companySegment`, `companyIndustry`, `companyRating`, `companyReviewCount`,\n  `companyOpenJobs`, `companySocialLinks[]`, `companyAddress`\n- `applicantLocationRequirement`, `streetAddress`, `jobImageUrl`, `directApply`\n\nEmpty values are omitted at every depth. `0` and `false` are preserved when they are real values.\n\n## Input reference\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `mode` | select | `search` | See modes above. |\n| `searchMode` | select | `classic` | `classic` or `semantic`. |\n| `searchQueries` | string list | `[]` | One or more keyword queries; deduplicated by job ID. |\n| `companyNames` | string list | `[]` | Company names for `byCompany`. |\n| `companyIds` | string list | `[]` | jobs.ch company IDs for `byCompany`. |\n| `startUrls` | URL list | `[]` | jobs.ch search or job-detail URLs for `byUrls`. |\n| `regions` | string list | `[]` | Region/canton IDs. |\n| `industries` | string list | `[]` | Industry IDs. |\n| `categories` | string list | `[]` | Occupational category IDs (all 165). |\n| `employmentTypes` | string list | `[]` | Contract-type IDs. |\n| `positions` | string list | `[]` | Seniority IDs. |\n| `languageSkills` | string list | `[]` | `de`, `en`, `fr`, `it`. |\n| `companySegments` | string list | `[]` | `gu`, `kmu`, `pdl`. |\n| `location` | text | `\"\"` | City/place refinement. |\n| `employmentGradeMin` | integer | `0` | Minimum workload %. |\n| `employmentGradeMax` | integer | `100` | Maximum workload %. |\n| `publishedSince` | select | `\"\"` | `1`, `3`, `7`, `30` days. |\n| `sortBy` | select | `relevance` | `relevance` or `date`. |\n| `includeDetails` | boolean | `false` | Fetch job detail and company profile. |\n| `descriptionFormat` | select | `text` | `text`, `html` or `both`. |\n| `maxDescriptionLength` | integer | `0` | Truncate descriptions; `0` = no limit. |\n| `maxItems` | integer | `20` | 1–2,000. |\n| `maxPages` | integer | `100` | 1–100 pages per query/filter combination. |\n| `locale` | select | `en` | `en`, `de`, `fr`. |\n| `incrementalMode` | boolean | `false` | Push only new/changed jobs. |\n| `stateKey` | text | `\"\"` | Key-value store key for incremental state. |\n\n## Examples\n\n### Classic keyword search\n\n```json\n{\n  \"mode\": \"search\",\n  \"searchQueries\": [\"software engineer\", \"data engineer\"],\n  \"regions\": [\"19\"],\n  \"employmentGradeMin\": 80,\n  \"maxItems\": 100\n}\n```\n\n### Semantic, cross-language search\n\n```json\n{\n  \"mode\": \"search\",\n  \"searchMode\": \"semantic\",\n  \"searchQueries\": [\"pflege\"],\n  \"location\": \"Genève\",\n  \"maxItems\": 50\n}\n```\n\n### Company openings with full profiles\n\n```json\n{\n  \"mode\": \"byCompany\",\n  \"companyNames\": [\"Swiss Medical Network\"],\n  \"includeDetails\": true,\n  \"maxItems\": 25\n}\n```\n\n### Paste search and detail URLs\n\n```json\n{\n  \"mode\": \"byUrls\",\n  \"startUrls\": [\n    {\"url\": \"https://www.jobs.ch/en/vacancies/?term=pflege&region=19\"},\n    {\"url\": \"https://www.jobs.ch/en/vacancies/detail/e5ce43b5-b97a-46cc-a783-93eff2bf064f/\"}\n  ],\n  \"includeDetails\": true\n}\n```\n\n### Browse by occupational field, newest first\n\n```json\n{\n  \"mode\": \"browse\",\n  \"categories\": [\"175\", \"176\"],\n  \"publishedSince\": \"7\",\n  \"sortBy\": \"date\",\n  \"maxItems\": 200\n}\n```\n\n## Use cases\n\n- Recruiting and labour-market monitoring by canton, industry or occupational field.\n- Company hiring-tracker dashboards built on stable `jobId` values.\n- Job-board aggregation with full descriptions and apply links.\n- Salary research using jobs.ch's own annual CHF estimates (always labelled as estimates).\n- Incremental monitoring of new or changed postings.\n\n## Data source\n\nThe actor reads jobs.ch's public search API, its server-rendered search pages for semantic mode, its company directory/profile API, and the job-detail pages' embedded React Query state plus schema.org `JobPosting` JSON-LD. All requests are anonymous, cookieless and direct.\n\n## Limitations\n\n- **2,000-row search ceiling.** jobs.ch returns at most 100 pages of 20 rows per search. Partition by filters, date windows or queries to collect more.\n- **Salary is an estimate.** The salary fields come from jobs.ch's own salary-estimation endpoint, are annual CHF, and are marked `salaryIsEstimate: true`. They are not employer-disclosed salaries.\n- **Contacts are partial.** Job-level contacts appear on a minority of postings (about 7 of 48 in testing); phone numbers are rarer and email addresses are not exposed.\n- **Apply links are external only.** About three-quarters of sampled jobs had an external apply URL. The rest use jobs.ch's on-site form; for those, `applyMethod` is `form` and `jobUrl` is the apply entry point.\n- **Company matching is best-effort.** Exact company names resolve first; otherwise prefix/contains matching is used. Company IDs are the reliable option.\n- **Semantic search is fuzzy by design.** It mirrors jobs.ch's own relevance engine, does not support date sorting, and can return off-topic rows for an unmatched query.\n\n## FAQ\n\n**Do I need a proxy, cookie or API key?** No. Every request is anonymous and direct. Existing tasks that still send a proxy object keep running; the value is ignored.\n\n**What is the difference between `industry` and `category`?** `industry` is the employer's business sector; `category` is the job's occupational function. A bank can post an IT-category job, so the two taxonomies are independent.\n\n**Why did I get fewer rows than `maxItems`?** Either jobs.ch had no more matches, the 2,000-row cap was reached, or `maxPages` stopped the crawl. The run's status message says which.\n\n**Why are some fields missing on some jobs?** jobs.ch does not include every field on every posting, and the actor omits empty values rather than inventing placeholders. Detail fields require `includeDetails: true`.\n\n**Are the salary figures employer-disclosed?** No. They are jobs.ch's annual CHF estimate and are always marked `salaryIsEstimate: true`.\n\n**How do I get past 2,000 rows?** Split the search into narrower slices — one region, category, company or publication window at a time — and combine the datasets. Deduplicate on `jobId`.\n\n**Does semantic search support sorting?** No. jobs.ch's semantic results page is relevance-only. Use classic search with `sortBy: \"date\"` for newest-first ordering.\n\n**What does incremental mode do?** With `incrementalMode: true`, the actor stores a signature per `jobId` under `stateKey` and pushes only new or changed records. Disable it for a full snapshot.\n\n**Can I use job URLs from any jobs.ch language?** Yes. English, German and French detail/search URLs are accepted, and the record URLs follow the URL's language.\n\n**Is this affiliated with jobs.ch?** No. It is an independent third-party actor that reads publicly available jobs.ch data.\n",
    "actorDefinition": {
      "actorSpecification": 1,
      "name": "jobs-ch-swiss-job-listings-by-region-and-industry",
      "title": "Jobs.ch Swiss Job Listings by Region & Industry",
      "description": "Scrape jobs.ch listings by keyword, company, URL, canton, industry, category or contract type. Returns full descriptions, salary estimates, apply links, contacts, benefits, languages and company ratings.",
      "version": "1.0",
      "buildTag": "latest",
      "defaultMemoryMbytes": 1024,
      "defaultRunOptions": {
        "memoryMbytes": 1024,
        "timeoutSecs": 3600
      },
      "dockerfile": "./Dockerfile",
      "readme": "# Jobs.ch Swiss Job Listings by Region & Industry\n\nScrape **jobs.ch**, Switzerland's largest job board, through its public search and company APIs. Run classic keyword searches, browse by filters, resolve employers by name or company ID, or paste jobs.ch search/job URLs. Listings can be enriched with the full description, apply URL and method, jobs.ch salary estimate, contacts, language requirements, occupational categories, regions, locations, application deadline, benefits and employer profile/ratings.\n\nNo login, cookies, API key or proxy is needed.\n\n## Modes\n\n| Mode | What it does |\n|---|---|\n| `search` | Classic keyword search across one or more queries. With `searchMode: \"semantic\"` it uses jobs.ch's fuzzy, cross-language search page instead. |\n| `browse` | Filter-only browsing with no keyword. |\n| `byCompany` | Resolves company names through jobs.ch's company directory and searches all openings. Company IDs are also accepted and are the most reliable option. |\n| `byUrls` | Accepts jobs.ch search URLs (their filters are preserved) and job-detail URLs. Detail URLs report `jobStatus`: `online`, `offline`, `expired` or `unknown`. |\n| `byRegion` | Legacy mode: requires at least one value in `regions`; filters only. |\n| `byIndustry` | Legacy mode: requires at least one value in `industries`; filters only. |\n| `byEmploymentType` | Legacy mode: requires at least one value in `employmentTypes`; filters only. |\n| `byCategory` | Legacy mode: requires at least one value in `categories`; filters only. |\n\n## Filters (all multi-value)\n\n- `regions` — 24 Swiss canton/language-region values.\n- `industries` — 24 employer-industry values.\n- `categories` — all **165** jobs.ch occupational categories (job function, distinct from employer industry).\n- `employmentTypes` — temporary, freelance, internship, supplementary income, permanent, apprenticeship.\n- `positions` — executive, specialist, employee.\n- `languageSkills` — German, English, French, Italian.\n- `companySegments` — large company, SME, personnel-service provider.\n- `location` — free-text city/place.\n- `employmentGradeMin` / `employmentGradeMax` — workload range in percent.\n- `publishedSince` — last 1, 3, 7 or 30 days.\n- `sortBy` — relevance or newest-first (classic search).\n\nMultiple values in one filter are combined with OR; different filters are combined with AND. Jobs.ch caps every search at **2,000 rows / 100 pages of 20**. Use narrower filters, date windows or several queries to get past the cap.\n\n## Output fields\n\nEvery record includes the job identity and listing fields:\n\n- `jobId`, `title`, `searchQuery` (when a keyword was used), `jobStatus` (URL mode only)\n- `companyId`, `companyName`, `companySlug`, `companyUrl`, `companyLogoUrl`\n- `location`, `locationDetails[]` (street, city, postal-code, canton, country, latitude, longitude)\n- `workloadMin`, `workloadMax`, `workloadText`\n- `contractType`, `contractTypes[]`, `seniorityLevel`\n- `tags[]`, `benefitTags[]`, `easyApply`, `quickApply`, `homeOfficeOffered`\n- `postedDate`, `initialPostedDate`, `postedRelative`, `initialPostedRelative`\n- `isNew`, `isBoosted`, `isPaid`, `isActive`, `headhunterApplicationAllowed`\n- `jobUrl`, `recordType`, `scrapedAt`\n\nWith `includeDetails: true`:\n\n- `description` (plain text), `descriptionHtml` (original HTML), `descriptionSnippet`\n- `applyUrl`, `applyMethod` (`external` or `form`), `applicationDeadline`\n- `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `salaryIsEstimate`\n- `contacts[]` (`name`, `firstName`, `lastName`, `role`, `phone`), plus `contactName` / `contactPhone`\n- `languageSkills[]` (`language`, `level`), `skills[]`, `benefits[]`\n- `category` (full taxonomy path), `categories[]`, `regions[]`, `cantonCode`\n- `roleSummary`, `industry`, `workHoursText`, `totalOpenings`, `startDate`, `jobImmediateStart`\n- `companyWebsite`, `companyOverview`, `companyDescription`\n- `companySize`, `companySegment`, `companyIndustry`, `companyRating`, `companyReviewCount`,\n  `companyOpenJobs`, `companySocialLinks[]`, `companyAddress`\n- `applicantLocationRequirement`, `streetAddress`, `jobImageUrl`, `directApply`\n\nEmpty values are omitted at every depth. `0` and `false` are preserved when they are real values.\n\n## Input reference\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `mode` | select | `search` | See modes above. |\n| `searchMode` | select | `classic` | `classic` or `semantic`. |\n| `searchQueries` | string list | `[]` | One or more keyword queries; deduplicated by job ID. |\n| `companyNames` | string list | `[]` | Company names for `byCompany`. |\n| `companyIds` | string list | `[]` | jobs.ch company IDs for `byCompany`. |\n| `startUrls` | URL list | `[]` | jobs.ch search or job-detail URLs for `byUrls`. |\n| `regions` | string list | `[]` | Region/canton IDs. |\n| `industries` | string list | `[]` | Industry IDs. |\n| `categories` | string list | `[]` | Occupational category IDs (all 165). |\n| `employmentTypes` | string list | `[]` | Contract-type IDs. |\n| `positions` | string list | `[]` | Seniority IDs. |\n| `languageSkills` | string list | `[]` | `de`, `en`, `fr`, `it`. |\n| `companySegments` | string list | `[]` | `gu`, `kmu`, `pdl`. |\n| `location` | text | `\"\"` | City/place refinement. |\n| `employmentGradeMin` | integer | `0` | Minimum workload %. |\n| `employmentGradeMax` | integer | `100` | Maximum workload %. |\n| `publishedSince` | select | `\"\"` | `1`, `3`, `7`, `30` days. |\n| `sortBy` | select | `relevance` | `relevance` or `date`. |\n| `includeDetails` | boolean | `false` | Fetch job detail and company profile. |\n| `descriptionFormat` | select | `text` | `text`, `html` or `both`. |\n| `maxDescriptionLength` | integer | `0` | Truncate descriptions; `0` = no limit. |\n| `maxItems` | integer | `20` | 1–2,000. |\n| `maxPages` | integer | `100` | 1–100 pages per query/filter combination. |\n| `locale` | select | `en` | `en`, `de`, `fr`. |\n| `incrementalMode` | boolean | `false` | Push only new/changed jobs. |\n| `stateKey` | text | `\"\"` | Key-value store key for incremental state. |\n\n## Examples\n\n### Classic keyword search\n\n```json\n{\n  \"mode\": \"search\",\n  \"searchQueries\": [\"software engineer\", \"data engineer\"],\n  \"regions\": [\"19\"],\n  \"employmentGradeMin\": 80,\n  \"maxItems\": 100\n}\n```\n\n### Semantic, cross-language search\n\n```json\n{\n  \"mode\": \"search\",\n  \"searchMode\": \"semantic\",\n  \"searchQueries\": [\"pflege\"],\n  \"location\": \"Genève\",\n  \"maxItems\": 50\n}\n```\n\n### Company openings with full profiles\n\n```json\n{\n  \"mode\": \"byCompany\",\n  \"companyNames\": [\"Swiss Medical Network\"],\n  \"includeDetails\": true,\n  \"maxItems\": 25\n}\n```\n\n### Paste search and detail URLs\n\n```json\n{\n  \"mode\": \"byUrls\",\n  \"startUrls\": [\n    {\"url\": \"https://www.jobs.ch/en/vacancies/?term=pflege&region=19\"},\n    {\"url\": \"https://www.jobs.ch/en/vacancies/detail/e5ce43b5-b97a-46cc-a783-93eff2bf064f/\"}\n  ],\n  \"includeDetails\": true\n}\n```\n\n### Browse by occupational field, newest first\n\n```json\n{\n  \"mode\": \"browse\",\n  \"categories\": [\"175\", \"176\"],\n  \"publishedSince\": \"7\",\n  \"sortBy\": \"date\",\n  \"maxItems\": 200\n}\n```\n\n## Use cases\n\n- Recruiting and labour-market monitoring by canton, industry or occupational field.\n- Company hiring-tracker dashboards built on stable `jobId` values.\n- Job-board aggregation with full descriptions and apply links.\n- Salary research using jobs.ch's own annual CHF estimates (always labelled as estimates).\n- Incremental monitoring of new or changed postings.\n\n## Data source\n\nThe actor reads jobs.ch's public search API, its server-rendered search pages for semantic mode, its company directory/profile API, and the job-detail pages' embedded React Query state plus schema.org `JobPosting` JSON-LD. All requests are anonymous, cookieless and direct.\n\n## Limitations\n\n- **2,000-row search ceiling.** jobs.ch returns at most 100 pages of 20 rows per search. Partition by filters, date windows or queries to collect more.\n- **Salary is an estimate.** The salary fields come from jobs.ch's own salary-estimation endpoint, are annual CHF, and are marked `salaryIsEstimate: true`. They are not employer-disclosed salaries.\n- **Contacts are partial.** Job-level contacts appear on a minority of postings (about 7 of 48 in testing); phone numbers are rarer and email addresses are not exposed.\n- **Apply links are external only.** About three-quarters of sampled jobs had an external apply URL. The rest use jobs.ch's on-site form; for those, `applyMethod` is `form` and `jobUrl` is the apply entry point.\n- **Company matching is best-effort.** Exact company names resolve first; otherwise prefix/contains matching is used. Company IDs are the reliable option.\n- **Semantic search is fuzzy by design.** It mirrors jobs.ch's own relevance engine, does not support date sorting, and can return off-topic rows for an unmatched query.\n\n## FAQ\n\n**Do I need a proxy, cookie or API key?** No. Every request is anonymous and direct. Existing tasks that still send a proxy object keep running; the value is ignored.\n\n**What is the difference between `industry` and `category`?** `industry` is the employer's business sector; `category` is the job's occupational function. A bank can post an IT-category job, so the two taxonomies are independent.\n\n**Why did I get fewer rows than `maxItems`?** Either jobs.ch had no more matches, the 2,000-row cap was reached, or `maxPages` stopped the crawl. The run's status message says which.\n\n**Why are some fields missing on some jobs?** jobs.ch does not include every field on every posting, and the actor omits empty values rather than inventing placeholders. Detail fields require `includeDetails: true`.\n\n**Are the salary figures employer-disclosed?** No. They are jobs.ch's annual CHF estimate and are always marked `salaryIsEstimate: true`.\n\n**How do I get past 2,000 rows?** Split the search into narrower slices — one region, category, company or publication window at a time — and combine the datasets. Deduplicate on `jobId`.\n\n**Does semantic search support sorting?** No. jobs.ch's semantic results page is relevance-only. Use classic search with `sortBy: \"date\"` for newest-first ordering.\n\n**What does incremental mode do?** With `incrementalMode: true`, the actor stores a signature per `jobId` under `stateKey` and pushes only new or changed records. Disable it for a full snapshot.\n\n**Can I use job URLs from any jobs.ch language?** Yes. English, German and French detail/search URLs are accepted, and the record URLs follow the URL's language.\n\n**Is this affiliated with jobs.ch?** No. It is an independent third-party actor that reads publicly available jobs.ch data.\n",
      "input": {
        "title": "jobs.ch Scraper Input",
        "description": "Search and browse jobs.ch job listings, resolve companies, or paste jobs.ch search and detail URLs.",
        "type": "object",
        "schemaVersion": 1,
        "properties": {
          "mode": {
            "title": "Mode",
            "type": "string",
            "description": "What to fetch. `search` runs classic keyword search (or semantic with `searchMode=semantic`). `browse` uses filters only. `byCompany` resolves company names/IDs. `byUrls` accepts jobs.ch search or job-detail URLs. The `byRegion`/`byIndustry`/`byEmploymentType`/`byCategory` modes are kept for backward compatibility and require their matching filter.",
            "editor": "select",
            "enum": [
              "search",
              "browse",
              "byCompany",
              "byUrls",
              "byRegion",
              "byIndustry",
              "byEmploymentType",
              "byCategory"
            ],
            "enumTitles": [
              "Keyword search",
              "Browse all jobs by filters",
              "Jobs at specific companies",
              "Search or job URLs",
              "Browse by canton / region",
              "Browse by industry",
              "Browse by employment type",
              "Browse by occupational field"
            ],
            "default": "search",
            "prefill": "search"
          },
          "searchMode": {
            "title": "Search relevance",
            "type": "string",
            "description": "`classic` uses jobs.ch's exact keyword API with all filters and sort. `semantic` uses the site's fuzzy, cross-language search page (filters still apply; sort is relevance-only).",
            "editor": "select",
            "enum": [
              "classic",
              "semantic"
            ],
            "enumTitles": [
              "Classic (exact keyword)",
              "Semantic (fuzzy, cross-language)"
            ],
            "default": "classic",
            "prefill": "classic"
          },
          "searchQueries": {
            "title": "Search queries",
            "type": "array",
            "description": "One or more free-text keywords (mode=search). Multiple queries are run in order and deduplicated by job ID. Leave empty to browse all jobs matching the filters.",
            "editor": "stringList",
            "items": {
              "type": "string"
            },
            "default": [],
            "prefill": [
              "software engineer"
            ]
          },
          "companyNames": {
            "title": "Company names",
            "type": "array",
            "description": "Company names to resolve against jobs.ch's company directory (mode=byCompany). Exact names are preferred; unmatched names fall back to a keyword search filtered by company name.",
            "editor": "stringList",
            "items": {
              "type": "string"
            },
            "default": [],
            "prefill": []
          },
          "companyIds": {
            "title": "Company IDs",
            "type": "array",
            "description": "jobs.ch company IDs (mode=byCompany). IDs are the most reliable company filter and take priority over names.",
            "editor": "stringList",
            "items": {
              "type": "string"
            },
            "default": [],
            "prefill": []
          },
          "startUrls": {
            "title": "Start URLs",
            "type": "array",
            "description": "jobs.ch search URLs or job-detail URLs (mode=byUrls). Search URLs carry their own filters; invalid or non-jobs.ch URLs are skipped.",
            "editor": "requestListSources",
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string",
                  "title": "URL",
                  "description": "jobs.ch search or job-detail URL."
                }
              },
              "required": [
                "url"
              ]
            },
            "default": [],
            "prefill": []
          },
          "regions": {
            "title": "Cantons / regions",
            "type": "array",
            "description": "Filter by one or more Swiss canton/language-regions (multi-value OR). Allowed IDs: 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25. Required for mode=byRegion; optional elsewhere.",
            "editor": "stringList",
            "items": {
              "type": "string"
            },
            "default": [],
            "prefill": []
          },
          "industries": {
            "title": "Industries",
            "type": "array",
            "description": "Filter by one or more employer industries (multi-value OR). Allowed IDs: 1-24. Required for mode=byIndustry; optional elsewhere.",
            "editor": "stringList",
            "items": {
              "type": "string"
            },
            "default": [],
            "prefill": []
          },
          "categories": {
            "title": "Occupational fields",
            "type": "array",
            "description": "Filter by one or more occupational fields / job functions (multi-value OR). Allowed IDs: 97-261 (165 jobs.ch categories; labels in README). Required for mode=byCategory.",
            "editor": "stringList",
            "items": {
              "type": "string"
            },
            "default": [],
            "prefill": []
          },
          "employmentTypes": {
            "title": "Employment types",
            "type": "array",
            "description": "Filter by one or more contract types (multi-value OR). Allowed IDs: 1 Temporary, 2 Freelance, 3 Internship, 4 Supplementary income, 5 Permanent position, 6 Apprenticeship. Required for mode=byEmploymentType.",
            "editor": "stringList",
            "items": {
              "type": "string"
            },
            "default": [],
            "prefill": []
          },
          "positions": {
            "title": "Seniority levels",
            "type": "array",
            "description": "Filter by one or more seniority levels (multi-value OR). Allowed IDs: 1 Executive position, 2 Specialist, 3 Employee.",
            "editor": "stringList",
            "items": {
              "type": "string"
            },
            "default": [],
            "prefill": []
          },
          "languageSkills": {
            "title": "Required languages",
            "type": "array",
            "description": "Filter by one or more required working languages (multi-value OR). Allowed values: de, en, fr, it.",
            "editor": "stringList",
            "items": {
              "type": "string"
            },
            "default": [],
            "prefill": []
          },
          "companySegments": {
            "title": "Company size",
            "type": "array",
            "description": "Filter by employer segment (multi-value OR). Allowed values: gu (large company), kmu (SME), pdl (personnel-service provider).",
            "editor": "stringList",
            "items": {
              "type": "string"
            },
            "default": [],
            "prefill": []
          },
          "location": {
            "title": "City / place",
            "type": "string",
            "description": "Optional free-text city/place refinement (e.g. Zürich, Geneva, Basel).",
            "editor": "textfield",
            "default": "",
            "prefill": ""
          },
          "employmentGradeMin": {
            "title": "Workload % — minimum",
            "type": "integer",
            "description": "Lower bound of the workload range in percent.",
            "minimum": 0,
            "maximum": 100,
            "default": 0,
            "prefill": 0
          },
          "employmentGradeMax": {
            "title": "Workload % — maximum",
            "type": "integer",
            "description": "Upper bound of the workload range in percent.",
            "minimum": 0,
            "maximum": 100,
            "default": 100,
            "prefill": 100
          },
          "publishedSince": {
            "title": "Published since",
            "type": "string",
            "description": "Only jobs first published within this time window.",
            "editor": "select",
            "enum": [
              "",
              "1",
              "3",
              "7",
              "30"
            ],
            "enumTitles": [
              "(Any time)",
              "Last 24 hours",
              "Last 3 days",
              "Last 7 days",
              "Last 30 days"
            ],
            "default": "",
            "prefill": ""
          },
          "sortBy": {
            "title": "Sort order",
            "type": "string",
            "description": "Classic search sort order: relevance or newest-first. Semantic search always uses relevance.",
            "editor": "select",
            "enum": [
              "relevance",
              "date"
            ],
            "enumTitles": [
              "Relevance",
              "Newest first"
            ],
            "default": "relevance",
            "prefill": "relevance"
          },
          "includeDetails": {
            "title": "Include job details & company profile",
            "type": "boolean",
            "description": "Fetch each job's detail page for full description, apply URL/method, salary estimate, contacts, languages, categories, regions, deadline, benefits and company profile/ratings. Adds one request per job plus one cached request per company.",
            "default": false,
            "prefill": false
          },
          "descriptionFormat": {
            "title": "Description format",
            "type": "string",
            "description": "Format of the enriched description: plain text, original HTML, or both.",
            "editor": "select",
            "enum": [
              "text",
              "html",
              "both"
            ],
            "enumTitles": [
              "Plain text",
              "HTML",
              "Both"
            ],
            "default": "text",
            "prefill": "text"
          },
          "maxDescriptionLength": {
            "title": "Max description length",
            "type": "integer",
            "description": "Truncate descriptions to this many characters (0 = no truncation).",
            "minimum": 0,
            "maximum": 50000,
            "default": 0,
            "prefill": 0
          },
          "maxItems": {
            "title": "Max items",
            "type": "integer",
            "description": "Maximum number of jobs to return. jobs.ch serves 20 rows per page and caps each search at 2,000 rows.",
            "minimum": 1,
            "maximum": 2000,
            "default": 20,
            "prefill": 3
          },
          "maxPages": {
            "title": "Max pages",
            "type": "integer",
            "description": "Maximum result pages to request per query/filter combination (jobs.ch caps searches at 100 pages).",
            "minimum": 1,
            "maximum": 100,
            "default": 100,
            "prefill": 1
          },
          "locale": {
            "title": "Language",
            "type": "string",
            "description": "jobs.ch site language for URLs and translated labels.",
            "editor": "select",
            "enum": [
              "en",
              "de",
              "fr"
            ],
            "enumTitles": [
              "English",
              "German (Deutsch)",
              "French (Français)"
            ],
            "default": "en",
            "prefill": "en"
          },
          "incrementalMode": {
            "title": "Incremental mode",
            "type": "boolean",
            "description": "When enabled, only new or changed jobs are pushed. State is stored under `stateKey` in the key-value store.",
            "default": false,
            "prefill": false
          },
          "stateKey": {
            "title": "Incremental state key",
            "type": "string",
            "description": "Key-value store key for incremental state (default `jobs_ch_incremental_state`). Only used when `incrementalMode` is true.",
            "editor": "textfield",
            "default": "",
            "prefill": ""
          }
        },
        "required": [
          "mode"
        ]
      },
      "output": {
        "actorOutputSchemaVersion": 1,
        "title": "Jobs.ch Swiss Job Listings by Region & Industry Output",
        "description": "jobs.ch job records with listings, descriptions, salary estimates, apply links, contacts, languages, categories, regions and company profiles.",
        "properties": {
          "jobs": {
            "type": "string",
            "title": "jobs.ch job listings",
            "description": "Dataset containing all scraped jobs.ch job listings.",
            "template": "{{links.apiDefaultDatasetUrl}}/items"
          }
        }
      },
      "storages": {
        "dataset": {
          "actorSpecification": 1,
          "views": {
            "overview": {
              "title": "Jobs",
              "transformation": {
                "fields": [
                  "title",
                  "companyName",
                  "location",
                  "workloadText",
                  "contractType",
                  "salaryMin",
                  "salaryMax",
                  "postedDate",
                  "jobUrl"
                ]
              },
              "display": {
                "component": "table",
                "properties": {}
              }
            }
          }
        }
      },
      "environmentVariables": {}
    },
    "buildNumber": "1.0.2",
    "imageDigest": "67af3dfad484feed1e5eb605ce1de46d73a45473069a827f71838119701debb3",
    "actVersion": {
      "sourceType": "SOURCE_FILES",
      "buildTag": "latest",
      "versionNumber": "1.0"
    },
    "usage": {
      "ACTOR_COMPUTE_UNITS": 0.006111111111111111
    }
  }
}