Sign inTry it

Search Swiss jobs on jobs.ch.

Searches jobs.ch, Switzerland's largest job board, like its search page: job title (with the workload, e.g. 80–100 %), company, place, employment type, when it was first published, and the link to the ad. About 20 jobs per page with the total number of matches. jobs.ch's search is fuzzy: a term with no real match still returns loosely related jobs.

No reports yetRead-only, no login
Site
jobs.ch, Switzerland
Version
v2 of 2
Runs, last 7 days
No reports yet
sha256
bd90902c26a3…

Run it with your agent

after installing Wield

Ask in plain words. Your agent finds this recipe and calls it:

“Search Swiss jobs on jobs.ch.”

Read-only: it changes nothing on the site. No login needed.

{
  "tool": "run",
  "arguments": {
    "recipe_id": "jobs.ch/search-jobs",
    "params": {
      "location": "Zürich",
      "term": "python"
    }
  }
}

Requests

sent from your browser, in order
  1. 1GET/de/stellenangebote/?location&page&termjobs.ch's search page (/de/stellenangebote/?term=…&location=…&page=…), server-rendered; its ld+json carries the page's 20 jobs as a schema.org ItemList of JobPosting.

Parameters

from your agent
locationstringCity, canton or postcode, e.g. 'Zürich', 'Bern', '8001'
pageinteger = 1
termstringrequiredJob title, skill or company, e.g. 'python', 'pflegefachfrau', 'buchhaltung'

Returns

to your agent
jobsobject[]company, employment_type, place, posted_at, title, url
locationstring
pageinteger
termstring
totalinteger

Where it can send

  • www.jobs.ch

Anything else is blocked by the extension.

Reads from your session

Nothing. Your browser attaches the site's cookies by itself.

Used only in requests to the addresses on the left. Never sent to wield.run or your agent.

Checks

a run passes when
  • length(output.jobs) >= `15`
  • output.total > `40`
  • length(output.jobs[?starts_with(url, 'https://www.jobs.ch/de/stellenangebote/detail/')]) == length(output.jobs)
  • length(output.jobs[?posted_at != `null` && company != `null`]) == length(output.jobs)
  • length(output.jobs[?contains(place || '', 'Zürich') || contains(place || '', 'Zurich')]) > `2`

Raw recipe

Served as canonical JSON from /api/v1/recipes/jobs.ch/search-jobs, so its sha256 matches the ETag and the index.

Check it yourself
curl -s https://wield.run/api/v1/recipes/jobs.ch/search-jobs | shasum -a 256
# bd90902c26a3e1d446e4502f7e5b7df1ba6b67fa599696d7e4cb389ae1a9f167
jobs.ch/search-jobs.json, v2
{
  "description": "Searches jobs.ch, Switzerland's largest job board, like its search page: job title (with the workload, e.g. 80–100 %), company, place, employment type, when it was first published, and the link to the ad. About 20 jobs per page with the total number of matches. jobs.ch's search is fuzzy: a term with no real match still returns loosely related jobs.",
  "effect": "read",
  "id": "jobs.ch/search-jobs",
  "keywords": [
    "jobs.ch",
    "stellenangebote",
    "jobs",
    "stellen",
    "schweiz",
    "zürich",
    "bern",
    "basel",
    "teilzeit",
    "festanstellung",
    "emploi",
    "job search",
    "switzerland",
    "vacancies"
  ],
  "output": {
    "schema": {
      "properties": {
        "jobs": {
          "items": {
            "properties": {
              "company": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "employment_type": {
                "description": "As jobs.ch shows it, e.g. Festanstellung, Temporär, Praktikum",
                "type": [
                  "string",
                  "null"
                ]
              },
              "place": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "posted_at": {
                "description": "ISO 8601 with offset",
                "type": [
                  "string",
                  "null"
                ]
              },
              "title": {
                "type": "string"
              },
              "url": {
                "type": "string"
              }
            },
            "type": "object"
          },
          "type": "array"
        },
        "location": {
          "type": [
            "string",
            "null"
          ]
        },
        "page": {
          "type": "integer"
        },
        "term": {
          "type": "string"
        },
        "total": {
          "type": [
            "integer",
            "null"
          ]
        }
      },
      "required": [
        "term",
        "jobs"
      ],
      "type": "object"
    },
    "value": "let $t = steps.search.title || '' in {term: params.term, location: params.location || `null`, total: (let $w = split(replace(replace($t, '’', ''), '\\'', ''), ' ') in (to_number($w[1]) && to_number(join('', [$w[0], $w[1]]))) || to_number($w[0])) || `null`, page: params.page || `1`, jobs: map(&{title: title, company: company || `null`, place: place || `null`, employment_type: type || `null`, posted_at: posted || `null`, url: url}, steps.search.jobs || `[]`)}"
  },
  "params": {
    "additionalProperties": false,
    "properties": {
      "location": {
        "description": "City, canton or postcode, e.g. 'Zürich', 'Bern', '8001'",
        "maxLength": 60,
        "minLength": 2,
        "type": "string"
      },
      "page": {
        "default": 1,
        "maximum": 20,
        "minimum": 1,
        "type": "integer"
      },
      "term": {
        "description": "Job title, skill or company, e.g. 'python', 'pflegefachfrau', 'buchhaltung'",
        "maxLength": 100,
        "minLength": 2,
        "type": "string"
      }
    },
    "required": [
      "term"
    ],
    "type": "object"
  },
  "provenance": {
    "method": "browser-inspect",
    "notes": "The search page /de/stellenangebote/?term=…&location=…&page=… (the ld+json SearchAction's urlTemplate; paging links use page=) is server-rendered. Its single ld+json script holds WebSite, CollectionPage, BreadcrumbList and an ItemList of the page's 20 JobPosting entries (identifier, title, datePosted, employmentType, hiringOrganization.name, jobLocation.address, url). The total is the number that starts the page title ('121 Python Jobs in Zürich', '3 245 Verkauf Jobs' with a narrow no-break space as thousands separator, which text extraction turns into a plain space, so the first two tokens are joined when both are digits). Pages hold 20–22 jobs; all are ordinary paid ads (isPaid), none marked as promoted. A nonsense term still finds 112 fuzzy matches (totalHits in the page state). The page also carries window state (__INIT__), which needs strip_js_after (schema field pending); the ld+json is enough. The page embeds the AWS WAF captcha SDK URL in its config, but no challenge was served. v2 (2026-11-08 08:34): verify only (jobs.ch writes the place as 'Zürich', 'Zurich', 'Zürich Oerlikon' or a nearby town, so the place check accepts both spellings and needs 3 of 20, not 6); requests and output unchanged.",
    "recorded_at": "2026-11-07T22:26:00+01:00",
    "recorded_by": "C4"
  },
  "requires_login": false,
  "schema": "https://wield.run/schema/recipe/v1.json",
  "site": {
    "country": "CH",
    "id": "jobs.ch",
    "language": "de",
    "name": "jobs.ch",
    "origin": "https://www.jobs.ch",
    "origins": [
      "https://www.jobs.ch"
    ]
  },
  "steps": [
    {
      "description": "jobs.ch's search page (/de/stellenangebote/?term=…&location=…&page=…), server-rendered; its ld+json carries the page's 20 jobs as a schema.org ItemList of JobPosting.",
      "expect": {
        "content_type": "html",
        "status": [
          200
        ]
      },
      "extract": {
        "embedded_json": {
          "required": false,
          "selector": "script[type=\"application/ld+json\"]",
          "vars": {
            "jobs": "[?\"@type\" == 'ItemList'] | [0].itemListElement[].item.{id: identifier.value, title: title, company: hiringOrganization.name, place: jobLocation.address.addressLocality, country: jobLocation.address.addressCountry, type: employmentType, posted: datePosted, url: url}"
          }
        },
        "html": {
          "title": {
            "selector": "title"
          }
        }
      },
      "id": "search",
      "request": {
        "headers": {
          "Accept": "text/html"
        },
        "method": "GET",
        "query": {
          "location": "{{params.location}}",
          "page": "{{params.page}}",
          "term": "{{params.term}}"
        },
        "url": "/de/stellenangebote/"
      }
    }
  ],
  "task": "Search Swiss jobs on jobs.ch",
  "tos": {
    "notes": "robots.txt allows the search page (it disallows /api/, login and feature-flag URLs) and has no rules for Claude agents or AI content signals. One request per call; no applications, job alerts or bookmarks.",
    "reviewed": false
  },
  "verify": {
    "checks": [
      "length(output.jobs) >= `15`",
      "output.total > `40`",
      "length(output.jobs[?starts_with(url, 'https://www.jobs.ch/de/stellenangebote/detail/')]) == length(output.jobs)",
      "length(output.jobs[?posted_at != `null` && company != `null`]) == length(output.jobs)",
      "length(output.jobs[?contains(place || '', 'Zürich') || contains(place || '', 'Zurich')]) > `2`"
    ],
    "example_params": {
      "location": "Zürich",
      "term": "python"
    },
    "max_ms": 4000
  },
  "version": 2
}