Count the jobs matching a filter set. Accepts the same filters as Search jobs (pagination, sorting, and description knobs are ignored) and returns only the total, which counts by any-listed-country (a job tagged with the country as any of its locations), matching Search jobs exactly. The default 90-day freshness window applies identically to the count. One qualifier: the count is sort-agnostic, so it also includes the small share of jobs (~2%) that carry no posted date — rows the default sort=posted_at pagination skips entirely and only sort=first_seen_at can enumerate. That only shows up when no date filter is active (a first_seen_since-only query, or status=closed with no dates); under the default 90-day window both endpoints exclude those jobs alike. The count is capped at 100000: when more jobs match, the response is { "count": 100000, "capped": true } instead of the exact total (the capped field is absent whenever the count is exact). The cap applies to EVERY count, including full-text ones — a broad q (especially with q_scope=full) is the most likely way to see capped: true. Supplying q counts full-text matches with the same syntax, scope tiers, symbol-token handling, synonym expansion, and guardrails as Search jobs, including the identical complexity budget: your own terms and operators must total at most 40 nodes, counted before synonym expansion (over budget returns 400 with detail = search query too complex: N terms/operators (max 40); a pure-exclusion q also returns 400; a q that still exceeds the time budget returns 422 — rarer now that the cap bounds the work, but unchanged in meaning). See Search jobs for the per-shape node costs and the synonym rules. When the full-text count matches nothing, the same single title-substring fallback that Search jobs runs fires here too and the response is flagged fuzzy: true, so sizing a query with a count before fetching it no longer reports 0 for a query that Search jobs answers from its fallback lane. Each endpoint still picks its lane from its OWN result, so the same posted-date qualifier above applies: when no date filter is active and every full-text match happens to carry no posted date, the count is a non-fuzzy non-zero while the default sort=posted_at search sees an empty page and falls back. Under any posted_at predicate — including the default 90-day window — the two always pick the same lane. level, workplace, and employment_type each accept MULTIPLE values — repeat the parameter or comma-separate them (e.g. level=mid,senior), exactly like country. A job is counted if it has any of the values given for that filter (within-field OR); different filters still narrow each other (across-field AND). The ONE exception is the location trio (country, state, location), which behaves as a single place selection and unions across the three — see Location filters below.
Location filters. country, state and location scope the count exactly as they scope a search, from the same GET /locations/search codes: country takes US, state takes US-TX, and location takes city and region slugs (dallas-tx-us, emea). All three are multi-value (repeated or comma-separated) and count a job if ANY of its resolved locations matches. The three together are ONE place selection: they UNION both within a filter and across the three, so count(state=US-NY&location=dallas-tx-us) is the size of New York ∪ Dallas — it is at least as large as either alone, never their (usually tiny) intersection. Nested selections resolve to the superset (country=us&state=US-CA counts the whole US). Every non-location filter still ANDs against that selection. Because adding a location code to an existing place selection can only grow the set (only the first code narrows, against no location filter at all), counting a broad location union is the most likely way to see capped: true — a 20-city location list plus a large country can exceed the cap. A code in the wrong parameter is rejected with 400 (location=us-ny → use state=US-NY), so a mis-namespaced code never silently inflates or zeroes a count. The former city= substring filter has been removed; like any unknown parameter it is now IGNORED rather than rejected, so a leftover city= silently counts everything.
Experience filters. experience_min / experience_max narrow the count exactly as they narrow a search: both read the job's STATED minimum requirement (experience_min_years, whole years 0-50), and jobs with no stated requirement are EXCLUDED whenever either is supplied — so an experience-filtered count is always smaller than the same count without them.
Visa sponsorship. sponsors_visa narrows the count exactly as it narrows a search, in the same three modes. not_false counts everything the posting does not rule out — confirmed sponsors plus the un-stated majority — dropping only confirmed non-sponsors; it is the headline mode, and the only one that keeps null-flag jobs. true counts CONFIRMED SPONSORS (the posting states sponsorship is available) and false counts confirmed non-sponsors (it explicitly states there is none); both EXCLUDE the null-flag jobs, so count(true) + count(false) is far below the unfiltered count — the gap is un-stated postings, not non-sponsoring employers — and count(not_false) is much larger than either. Coverage of the flag is still thin, with true much rarer than false; counting without the filter is the way to size the full catalog. Values are case-insensitive, and 1 / 0 are accepted as true / false.
Seniority levels. level is derived, not copied from the posting: it reflects the strongest employer signal, in order: explicit title keyword → seniority set in the employer's ATS → stated years-of-experience requirement (0–1 entry / 2–4 mid / 5+ senior) → conservative title fallbacks. A level filter excludes unclassified (null) jobs, which are a large share of the corpus. See Search jobs for the per-level definitions table.
Authorization
bearerAuth Provide your secret API key (prefixed sk_) as a bearer token: Authorization: Bearer sk_....
In: header
Query Parameters
Case-insensitive substring match on the job title (3-200 characters).
3 <= length <= 200Full-text search across the job. Google-style syntax: quoted "exact phrase", OR, and -term exclusion (a query that is ONLY exclusions is rejected with 400). Operator precedence: implicit AND binds tighter than OR, and a -term attaches only to its adjacent group — so "machine learning" OR "data scientist" -senior parses as A OR (B AND NOT senior) and CAN still return Senior ML titles. To exclude a term everywhere, distribute it across each branch: "machine learning" -senior OR "data scientist" -senior. COMPLEXITY BUDGET: the terms and operators of YOUR query must total at most 40 nodes, counted on the parsed query before any server-side expansion, so the cost of a query is exactly what you can see. Over budget returns 400 with detail = search query too complex: N terms/operators (max 40) (N is your query's node count). Per-shape costs: a bare word is 1 node plus 1 for each OR/AND joining it (so ~20 OR'd words), a quoted two-word phrase is 3 nodes plus 1 for its OR (~10 phrases), a quoted three-word phrase is 5 plus 1 (~6), and a -term exclusion is 3. English stopwords are dropped while parsing and cost nothing — including INSIDE a phrase, so "director of engineering" costs 3 nodes (it parses as a two-word phrase), not 5. The OR'd multi-phrase "title picker" shape ("frontend developer" OR "backend developer" OR ...) is first-class — up to about 10 two-word phrases. q is also capped at 300 characters, but the node budget is normally the binding limit. SYNONYMS: terms are expanded server-side against a curated role vocabulary (e.g. developer also matches Programmer/Engineer titles, common abbreviations such as ts, and qa → quality assurance). That expansion NEVER counts against your budget, and if it would grow the query extremely large the search silently runs WITHOUT synonym enrichment rather than erroring — your exact terms always still match. Expansion is DIRECTIONAL by curation: developer reaches Engineer/Programmer titles, but engineer does NOT reach Developer titles (deliberate — "Development"/"Developer" share a stem, so that direction would pull in "Business Development Manager"-style titles). For full role-family coverage include both words; the budget has room. By default q matches the job TITLE only (precise — best for common role terms); pass q_scope=full to also search the description. Phrase queries under q_scope=full combined with very narrow filters (a small country, a rare language) can hit the search timeout; the default title scope is immune, so prefer it for phrase precision. q composes as a filter with the standard sort/cursor pagination (newest-first over the matches) and respects the default 90-day freshness window unless a date filter is given. Symbol-bearing tech tokens (c++, c#, .net) are matched as a title substring instead of full-text (they collapse under English tokenization). If a full-text search yields no rows a single title-substring fallback runs and its results are flagged fuzzy: true.
2 <= length <= 300Which fields q searches. title (default) matches the job title only — precise, best for common role terms (e.g. developer, engineer), which otherwise match description boilerplate. full also searches the description — broader reach, best for niche skills or tools that appear only in the requirements text (a language or framework not named in the title). Only affects results when q is set (ignored otherwise).
"title"Value in
- "title"
- "full"
Filter by ISO 3166-1 alpha-2 country code(s). Repeat the parameter or comma-separate values (e.g. country=us,de). A job matches if it is tagged with any of the given countries. Up to 20 values; case-insensitive. country, state and location form ONE place selection: a job matches if ANY of its resolved locations matches ANY selected code of ANY of the three kinds, so country=de&location=dallas-tx-us returns German jobs PLUS Dallas jobs (a union, not an intersection). Nested selections resolve to the superset — country=us&state=US-CA is just the US. When a bare state code is also supplied, the country additionally scopes that code (see state).
items <= 20Filter by state / region reference code(s). Use full <COUNTRY>-<ADMIN1> codes such as state=US-CA,US-NY (repeat the parameter or comma-separate; case-insensitive). To filter to ONLY a state, pass the full code alone — do NOT also pass its country: country, state and location form ONE place selection (a job matches if ANY of its resolved locations matches ANY selected code of ANY of the three kinds), so country=us&state=US-CA selects ALL United States jobs — the country unions with the state rather than narrowing it, exactly as state=US-NY&location=dallas-tx-us returns New York jobs PLUS Dallas jobs and state=US-TX&location=dallas-tx-us is just Texas (nested selections resolve to the superset). If country IS supplied, bare admin1 codes are accepted as a spelling convenience and auto-scoped to each given country — country=us&state=CA reads CA as US-CA (with multiple countries a bare code expands against each: country=us,ca&state=CA → US-CA,CA-CA) — but the countries themselves stay in the selection, so this form still selects the whole country; it is useful only when you genuinely want the country AND are abbreviating its states. A bare code WITHOUT country is ambiguous and matches nothing — pass the full US-CA form. Look up valid codes via GET /locations/states?country=<code> (its code field is the full value; admin1_code is the bare form). A job matches if it is tagged with any of the resulting regions. Up to 20 region codes, counted AFTER auto-scoping. A city / region slug is NOT accepted here and is rejected with 400: city slugs (dallas-tx-us, singapore-sg) and the five region slugs (amer, apac, emea, latam, worldwide) belong in location.
items <= 20Filter by canonical city / region code(s). Values are the EXACT code of a GET /locations/search row whose kind is city or region — a city slug such as dallas-tx-us, or one of the five region slugs (amer, apac, emea, latam, worldwide). Repeat the parameter or comma-separate values (e.g. location=dallas-tx-us,emea); values are case-insensitive and trimmed. A job matches if any of its resolved locations carries any of the given codes (within-field OR), so a job listed in both Dallas and Austin matches either. Up to 20 values. Codes are opaque — pass a /locations/search code through verbatim rather than composing one by hand; an unknown code simply matches nothing. country, state and location form ONE place selection: a job matches if ANY of its resolved locations matches ANY selected code of ANY of the three kinds, so location=dallas-tx-us&state=US-NY returns Dallas jobs PLUS New York jobs (a union, not an intersection). Nested selections resolve to the superset — location=dallas-tx-us&state=US-TX is just Texas. Country and state codes BELONG IN the country / state filters and are rejected here with 400 rather than silently matching nothing: a two-letter value (us) is a country code, and a <COUNTRY>-<ADMIN1> value (us-ny, de-by) is a state code. The kind field on each /locations/search suggestion tells you which of the three filters its code belongs to.
items <= 20Filter by workplace arrangement. Repeat the parameter or comma-separate values (e.g. workplace=remote,hybrid). A job matches if it has any of the given values.
items <= 3Filter by employment type. Repeat the parameter or comma-separate values (e.g. employment_type=full_time,contract). A job matches if it has any of the given values.
items <= 5Filter by seniority level. Repeat the parameter or comma-separate values (e.g. level=mid,senior). A job matches if it has any of the given values. level is derived, not copied from the posting: it reflects the strongest employer signal, in order: explicit title keyword → seniority set in the employer's ATS → stated years-of-experience requirement (0–1 entry / 2–4 mid / 5+ senior) → conservative title fallbacks. Jobs we could not classify (level is null) are EXCLUDED whenever this filter is supplied.
items <= 7Keep jobs whose stated MINIMUM years-of-experience requirement is at least this many years (experience_min_years >= n). Both experience filters read the job's stated minimum requirement — never its upper bound — so experience_min=5 returns the jobs asking for 5 or more years. Jobs with no stated requirement (experience_min_years is null) are EXCLUDED whenever either experience filter is supplied. Whole years, 0-50.
0 <= value <= 50Keep jobs whose stated MINIMUM years-of-experience requirement is at most this many years (experience_min_years <= n) — i.e. experience_max=3 returns the jobs a candidate with 3 years of experience qualifies for. Like experience_min it reads the job's stated minimum requirement, never its upper bound. Jobs with no stated requirement (experience_min_years is null) are EXCLUDED whenever either experience filter is supplied. Whole years, 0-50.
0 <= value <= 50Filter by ISO 639-1 language code (2 letters, case-insensitive).
2 <= length <= 2Keep jobs whose stated salary range reaches at least this amount. Jobs that state only a minimum (no maximum) match when that minimum is at least this value.
0 <= valueKeep jobs whose stated salary range starts at or below this amount.
0 <= valueFilter by ISO 4217 currency code (3 letters, case-insensitive).
3 <= length <= 3When true, return only jobs that specify a salary; when false, only jobs without a salary.
Filter on the tri-state visa-sponsorship flag (sponsors_visa), extracted from the posting text. This is a THREE-MODE selector, not a boolean. not_false keeps every job whose posting does not rule sponsorship out — confirmed sponsors plus the very large majority whose posting says nothing — and drops only the postings that explicitly refuse; for visa-dependent candidates this is the practical default: keeps confirmed sponsors and unknowns, drops only confirmed non-sponsors. true is the strict answer: CONFIRMED SPONSORS only — postings that affirmatively state sponsorship is available. That set is narrow (only a small fraction of postings state a policy at all, and confirmed sponsors are far rarer than confirmed refusals), so reach for it when you want certainty rather than coverage. false returns CONFIRMED NON-SPONSORS: postings that explicitly say there is no sponsorship, including a hard U.S.-citizenship eligibility requirement — an exclusion list, not a job search. Note this differs from has_salary, which is a presence check: sponsors_visa=false means "the posting says no", NOT "the posting is silent". Most postings say nothing and carry sponsors_visa: null — NULL means UNKNOWN, never "no". not_false is the ONLY mode that keeps those null-flag jobs; true and false both EXCLUDE them, so true and false do not add up to the unfiltered total and not_false is not their union. Omit the parameter to search everything regardless of the flag. In a query string the value is case-insensitive, and 1 / 0 are accepted as true / false. Coverage of the underlying flag grows as more postings are processed.
Filter by the numeric company id.
Filter by listing status. open (default) returns active listings; closed returns delisted ones.
"open"Value in
- "open"
- "closed"
Only jobs posted at or after this ISO-8601 timestamp, including a UTC offset (e.g. 2024-01-01T00:00:00+00:00). When none of posted_since, posted_until, or first_seen_since are provided and status is open, results default to the last 90 days.
date-timeOnly jobs posted at or before this ISO-8601 timestamp, including a UTC offset (e.g. 2024-01-01T00:00:00+00:00).
date-timeOnly jobs first seen at or after this ISO-8601 timestamp, including a UTC offset (e.g. 2024-01-01T00:00:00+00:00).
date-timeResponse Body
application/json
application/problem+json
application/problem+json
application/problem+json
application/problem+json
curl -X GET "https://example.com/api/v1/jobs/count"{ "count": 0, "capped": true, "fuzzy": true}{ "type": "string", "title": "string", "status": 0, "detail": "string", "errors": [ { "field": "string", "message": "string" } ]}{ "type": "string", "title": "string", "status": 0, "detail": "string", "errors": [ { "field": "string", "message": "string" } ]}{ "type": "string", "title": "string", "status": 0, "detail": "string", "errors": [ { "field": "string", "message": "string" } ]}{ "type": "string", "title": "string", "status": 0, "detail": "string", "errors": [ { "field": "string", "message": "string" } ]}