Skip to main content
Reference material for Search Jobs: filter grammar and operators, common indexed fields, the full Job catalog, id semantics, aggregation bucket metadata, null behavior, and errors. For worked examples, see Examples. For sorting, pagination, field selection, and aggregations, see Pagination & sorting.
Jobs ID cheat sheet. The Jobs APIs use three id concepts — keep them straight:
  • crustdata_job_id — the Crustdata job identifier. Returned on every Job. Use it as your dedupe key.
  • company.basic_info.crustdata_company_id — the Crustdata company identifier returned on every Job.
  • company.basic_info.company_id (filter alias) — the dot-path used in filters and aggregations.column for indexed Search Jobs. It points to the same integer as company.basic_info.crustdata_company_id. This alias is not sortable; for deterministic pagination, sort on metadata.date_added instead.
When you group_by on company.basic_info.company_id, each bucket also returns metadata.company_name, metadata.company_website_domain, and metadata.linkedin_id for labeling.

Filter grammar

Every filter describes which individual job rows to keep. The API checks each job listing against your filter independently — it never groups or combines rows before filtering. There are two building blocks:
Exact-match AND on the same field always returns zero results. One listing has one title, so (title = "Software Engineer") AND (title = "Account Executive") can never match. This applies to = and in.
All-words operators ((.)) work fine in AND. Because (.) checks for individual words — not a contiguous substring — a query like (title (.) "Software Development") AND (title (.) "Software Engineer") matches any title containing all three words “Software”, “Development”, and “Engineer” (e.g. “Software Development Engineer”).
Need companies hiring for both role X and role Y (two different listings)? Run two separate queries and intersect company ids client-side. See Companies indexing both Software Engineers and Account Executives.

Single condition

AND / OR group

Array-field filters and grouping

When you filter on a string-array field like company.basic_info.industries, the condition is satisfied if any element of the array matches.For example:
This matches any company whose industries array contains that exact string. Use (.) to match words within any element.
When you group_by on an array field, each array element becomes its own bucket key. A company in two industries contributes one count to each of the two industry buckets — so the sum of bucket counts can exceed total_count for array fields.

Filter operators

Use the table below to pick the right type for each condition. Every operator works on indexed fields only.
Operator footguns.
  • Use => for greater-than-or-equal and =< for less-than-or-equal — they are not >= and <=.
  • in and not_in require JSON arrays, not comma-separated strings.
  • is_null / is_not_null require the value key — send "value": null. Omitting value returns 400.
  • geo_distance / geo_exclude only work on location and location.raw. Using them on any other field, or sending a malformed geo value, returns 500 — see Errors.

Geographic radius filters (geo_distance / geo_exclude)

The value for geo_distance and geo_exclude is an object describing a center point and a radius: Jobs within 25 km of San Francisco:
US jobs outside a 50-mile radius of New York, combining geo_exclude with other conditions in an and group:
If your center point comes from your own geocoder or a map UI, pass lat_lng directly — for example { "lat_lng": [37.7749, -122.4194], "distance": 25 } — and the server-side geocoding step is skipped.

Common indexed fields

These are the indexed fields most often used in filters, sorts, and aggregations.field. This table is a summary of the most common paths, not an authoritative catalog. For the deeper field catalog — including id semantics, null handling, and bucket metadata — see the full Field reference below.
Company id filter alias. The filterable field path uses the short alias company.basic_info.company_id, but the response shape returns the same integer at company.basic_info.crustdata_company_id. They point to the same value. See Jobs IDs: a quick map.
Sending a filter on a non-indexed field returns 500 with Unsupported columns in conditions: ['...']. Sending an unsupported group_by field returns a similar 500 listing every supported aggregation field.

Field reference

This section covers the return shape, id semantics, aggregation bucket metadata, and the most important indexed field catalogs in one place.

Annotated full Job example

The code fence below uses jsonc because it includes inline // comments for annotation. Strip the comments before sending it to a strict JSON parser.
Nulls are normal. Nested objects such as revenue.public_markets, location.district, location.pincode, and parts of company.funding can legitimately be null or missing.

Jobs IDs: a quick map

Aggregation bucket metadata

When you group_by on company.basic_info.company_id, each bucket carries a metadata object whose keys use bucket-specific names rather than the Job response dot-paths:

Job identifiers

Job details (job_details.*)

job_details.source — posting source platform. Every job carries exactly one of these values: professional_network (listings from professional networking platforms), or the platform of the company job board that published the posting — workday, smartrecruiters, greenhouse, workable, lever, icims, ashby, rippling, or yc. Filter with = / in-style operators or group_by on it; it is not sortable.
job_details.employment_type — employment type declared by the posting. One of full_time, part_time, contract, intern, or temporary. Currently populated for roughly 85% of postings published on company job boards, and null for now when source is professional_network — treat missing values as “not declared”, not as a specific type. Filterable and groupable; not sortable.
Search-only fields. job_details.source and job_details.employment_type are available on POST /job/search only — Live Search does not return them.

Company basic info (company.basic_info.*)

company.basic_info.company_id and company.basic_info.crustdata_company_id refer to the same integer. Use the short alias in filters and aggregations.field. The response shape writes the value under crustdata_company_id.

Company firmographics

Headcount (company.headcount.*)

Followers (company.followers.*)

Revenue (company.revenue.*)

Funding (company.funding.*)

Competitors and company locations

Job location (location.*)

The city, state, and country fields are derived by geocoding the raw location string, so they carry normalized place names rather than the raw text’s wording. city can be an empty string "" when the raw location resolves to an area broader than a city (for example "San Francisco Bay Area" geocodes to state: "California" with an empty city).
Country values are geocoded and normalized. location.country carries normalized full country names ("United States", "United Kingdom"). A small share of rows still carries a residual variant such as "United States of America". When completeness matters, match both with in, or pre-discover the exact indexed values by running a group_by on location.country.

Content (content.*)

To find listings by an exact technology, skill, brand, or keyword, use [.] on content.description — it guarantees the literal word or phrase is present. (.) is typo-tolerant, so a single keyword can match lookalike words instead ((.) "vitally" also matches descriptions that contain only “virtually” or “finally”). Use (.) for multi-word descriptive matching.

Metadata (metadata.*)

Null, blank, and sparse field behavior

Most Job fields are nullable in the spec and can legitimately be absent or empty.
  • Null or missing — the field is not present on a given Job.
  • Blank string "" — the field was present but had no indexable value (common for job_details.workplace_type). Treat blank as “unspecified”, not as the same thing as null.
  • Sparse nested objectscompany.funding, company.revenue, and company.competitors are often missing for smaller or private companies.
  • is_null / is_not_null operators filter on null or missing fields directly — send "value": null (the value key is required).

Errors

Every error — including 401 — uses the same nested envelope: { "error": { "type", "message", "metadata" } }. Branch on error.type rather than string-matching message.

Pagination & sorting

How to paginate, sort, select fields, and aggregate results in Search Jobs. For worked examples, see Examples. For filter grammar, operators, and the full field catalog, see Reference.
Replace YOUR_API_KEY in each example with your actual API key. All requests require the x-api-version: 2025-11-01 header.

Sorting

sorts is an ordered array. Each item has a field and order ("asc" or "desc"). Sorts apply in array order — the first sort is the primary key, the second breaks ties, and so on.
Sort allowlist is narrower than filter allowlist. Sort only works on numeric, date, and a small set of scalar fields. Sorting on text fields like job_details.title, job_details.category, or company.basic_info.name returns Unsupported columns in conditions.

Sortable fields

The following indexed fields are verified sortable:
  • crustdata_job_id
  • metadata.date_added
  • metadata.date_updated
  • company.headcount.total
  • company.followers.count
  • company.revenue.estimated.lower_bound_usd
  • company.funding.last_fundraise_date
  • company.funding.num_funding_rounds
  • relevance — only valid when a text query is present in filters (maps to the text-match score); sorting on it without a text condition returns 400
Common sort choices:
  • Newest postings first{ "field": "metadata.date_added", "order": "desc" }
  • Biggest companies first{ "field": "company.headcount.total", "order": "desc" }
  • Most followed companies first{ "field": "company.followers.count", "order": "desc" }
  • Most funding rounds first{ "field": "company.funding.num_funding_rounds", "order": "desc" }

Pagination

Pagination is cursor-based. Each response returns a next_cursor (or null when you reach the end). To fetch the next page, resend the original request body with cursor set to the previous next_cursor.
1

Fetch the first page

Omit cursor and set limit to your page size (max 1000).
2

Walk forward

Take next_cursor from the response and pass it back as cursor in the next request. Keep filters, sorts, and fields identical — if you change them, the cursor becomes meaningless.
3

Stop when `next_cursor` is null

A null cursor means you’ve reached the end of the result set.

Consistency between pages

Best-effort, not strict snapshot. A cursor is consistent with respect to the filter, sort, and field selection you sent on the first page, so the same query will keep paging forward over a coherent result stream. However, because the underlying indexed dataset is continuously updated, new jobs indexed between page requests can cause minor drift in total_count and in the exact position of individual rows. Treat pagination as best-effort, not a strict snapshot.For bulk exports where every row matters:
  • Constrain your filter to a bounded date window (for example metadata.date_added >= 2025-01-01 AND < 2025-07-01) so newly indexed jobs outside the window do not affect the walk, and
  • Re-run the full walk periodically and diff against the prior snapshot using crustdata_job_id as the dedupe key.

Dataset freshness and lifecycle

What the indexed Jobs dataset represents. The Search Jobs dataset is a rolling index of job listings discovered from the web, refreshed on an ongoing basis. Each row has:
  • metadata.date_added — when Job was posted.
Closed or removed listings are not guaranteed to disappear from the index immediately. To approximate “currently hiring” queries, filter on a recent metadata.date_added window (for example, within the last 30 days) and pair it with the hiring company’s firmographics. For alerting or repeated exports, keep your date windows bounded and dedupe rows with crustdata_job_id.

Date filter semantics

Dates and timezones. When you pass a date-only value like "2025-01-01", the backend interprets it as 2025-01-01T00:00:00 in UTC. Ranges using => are inclusive of the boundary and < is exclusive, so "metadata.date_added" >= "2025-01-01" AND < "2025-07-01" covers every listing indexed between Jan 1 (inclusive) and Jul 1 (exclusive) in UTC. Pass full timestamps like "2025-01-01T08:00:00" when you need finer precision.

Fetch page 2


Field selection

Use fields to return only the dot-paths you need. The top-level groups are crustdata_job_id, job_details, company, location, content, metadata. You can request:
  • A whole group"company" returns every company.* sub-object.
  • A sub-object"company.basic_info" returns only the basic info block.
  • A single field"company.basic_info.name" returns just the name.
Recommended default field set for most dashboards: ["job_details.title", "job_details.category", "job_details.url", "company.basic_info.name", "company.basic_info.primary_domain", "location.raw", "metadata.date_added"].

Aggregations

Aggregations let you roll up results without returning individual job rows. Set limit: 0 when you only want aggregation output. Two types are supported:
  • count — returns the total number of jobs matching filters.
  • group_by — buckets the results by field and returns per-bucket counts.

AggregationRequest schema

Each AggregationResponseItem echoes type and field, then carries:
  • value (integer) — populated for count aggregations. The total match count.
  • buckets (array) — populated for group_by aggregations. Each bucket has a key, count, and a metadata object whose keys depend on the grouped field. See Aggregation bucket metadata.
You can include multiple aggregations in a single request; the response returns them in aggregations[] in the same order you sent them.

Count all Engineering jobs

Top companies indexing “Software Engineer” listings (bounded window)

Groupable fields

group_by.field is restricted to the following indexed fields:
  • company.basic_info.company_id
  • company.basic_info.crustdata_company_id
  • company.basic_info.industries
  • company.basic_info.primary_domain
  • company.funding.last_round_type
  • company.headcount.range
  • company.locations.country
  • job_details.category
  • job_details.employment_type
  • job_details.source
  • job_details.title
  • job_details.workplace_type
  • location.country
Sending any other field returns 500 with Unsupported aggregation field: '...'. Supported: ....

What’s next

  • Search Jobs — back to the main Search page.
  • Examples — SDR/BDR keyword hunting, mid-market filtering, funding-triggered queries, and aggregations.
  • Pagination & sorting — sorting, pagination, field selection, and aggregations.
  • OpenAPI reference — the formal schema for every request, response, and error.