{"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}