Search live job listings using job-seeker criteria expressed in natural language.
USE THIS TOOL WHEN
Use jobs.search when the user wants to discover, browse, refine, or continue searching for employment opportunities. This includes role/skill searches, company-specific searches, location and radius searches, employment type, salary criteria, freshness, and requests such as "show me more" that continue an established job search.
DO NOT USE THIS TOOL WHEN
This tool ONLY discovers live job openings. For anything that is not a request to find matching job openings, do not call it — answer directly or explain that this app only searches job listings.
Do not call jobs.search for non-job-search tasks. Examples that must NOT trigger this tool: weather or forecasts; flight, hotel, or other travel booking; food ordering or restaurant search; sports scores; directions, maps, or navigation; general knowledge, math, or translation; coding or technical help; resume or cover-letter writing; interview coaching; salary negotiation advice; general career advice; and general company or business research. Call jobs.search only when the user is actually asking to find job openings (which may accompany one of the above — e.g. "resume tips AND find me PM jobs in Austin" does warrant a jobs.search for the job-finding part).
HOW TO BUILD THE SEARCH
1. Populate only criteria stated by the user or already established in the current job-search conversation.
2. Default to a fresh search. Treat each new job-search request as standalone and build its filters ONLY from that turn, unless the turn is clearly an incremental refinement of the immediately preceding search. Carry criteria forward ONLY in the refinement case; otherwise drop all prior filters (company, query text, location, employment type, salary, freshness) and start clean.
- It is a refinement (preserve prior criteria) only when the turn cannot stand on its own and explicitly builds on the last search — e.g. "make that remote", "only full-time", "show me more", "what about within 25 miles", "raise the salary to 150k", "same but in Boston".
- It is a NEW search (drop prior criteria) when the turn names a new role, company, or location and reads as a complete request on its own — e.g. after "jobs at Amazon", the turn "find marketing jobs in New York, NY" is a new search: do NOT carry "Amazon" forward. When in doubt, treat it as a new search rather than merging.
3. Do not invent employers, locations, employment types, salary constraints, or other filters. Never combine an employer from a prior turn with an unrelated role or location from the current turn.
4. The free-text query is optional. Use it for role titles, skills, domains, specialties, or other job-search keywords (including preferences the tool has no structured filter for, such as "remote" or seniority — put those in the query text). A search may instead be driven entirely by structured filters. Send at least one of query, locations, employmentTypes, companies, salary, or postedWithinDays. The query is matched as PLAIN natural-language text, not a boolean search: do NOT wrap terms in quotation marks or use operators such as OR, AND, or parentheses. Write 'senior software engineer', never '"Senior Software Engineer" OR "Lead Software Engineer"'.
- Employer searches use companies.include, e.g. "jobs at Amazon" -> {"companies": {"include": ["Amazon"]}} with no query. There is no "company" parameter; a name that is not in the schema is dropped before the request is sent, which can leave the call empty and make it fail.
5. Normalize obvious user wording to schema values. Example: "full time" -> FULL_TIME. Use postedWithinDays for freshness requests; the backend does not provide a reliable explicit sort control. Do not add a filter merely because it seems likely.
6. Location is optional. If the user gives none and none is established, run the search without locations — it defaults to a nationwide search in the default country (US). Do not ask for a location merely because none was provided. For a normal location search, send locations[].place; geo coordinates are not required. For a radius search, send locations[].place, locations[].radius {value, unit}, AND locations[].geo {latitude, longitude} — numeric coordinates of the search center from your own knowledge of the named place. Radius requires geo: never omit geo when a radius is set, and never silently drop the radius or downgrade to a place-only search. If the place is too ambiguous to place reliably, ask the user to clarify rather than guessing. If a radius is requested without any location and none is established, ask for the location first.
7. For salary, normalize shorthand amounts such as "150k" to 150000. Only send a salary filter when the user's intended amount, currency, and period are sufficiently clear from the request or established context; otherwise ask a brief clarifying question.
8. Use postedWithinDays for relative freshness requests such as "past week" or "in the last month". The maximum is 90 days; for any longer window (e.g. "in the last 6 months") use 90 rather than a larger value.
9. For "show me more", "next page", or equivalent continuation follow-ups (a refinement per rule 2), keep the established search criteria and advance pagination using the nextOffset returned by the previous result. Do not apply pagination continuation to a new, standalone search — start it at the default offset.
10. Empty results are valid. Never claim openings exist unless they are returned by the tool.
11. After a successful call, prefer the jobs widget when available. In conversational text, summarize the result set rather than duplicating a long list of jobs.
12. Issue exactly ONE jobs.search call per user request. Do not immediately re-run the same intent with a reworded, reformatted, or boolean version of the query to "improve" it — treat the first result set as authoritative. Call again only when the user asks for more results (pagination, per rule 9) or changes/refines the criteria (rule 2).
RESULT HANDLING
A successful response contains jobs plus pagination metadata. An empty jobs array is a successful search with no current matches.
A failed response carries an `error` object. Do not fabricate job results; explain the failure concisely.
jobs.search