> ## Documentation Index
> Fetch the complete documentation index at: https://docs.crustdata.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Jobs Search reference

> Reference for Crustdata Jobs Search: common indexed fields, full field catalog, id map, bucket metadata, and errors.

Reference material for [Search Jobs](/job-docs/search/introduction): 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](/job-docs/search/examples). For sorting,
pagination, field selection, and aggregations, see
[Pagination & sorting](#pagination--sorting).

<Note>
  **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](/job-docs/search/introduction). 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.
</Note>

***

## 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:

| Building block                    | What it does                                                                 |
| --------------------------------- | ---------------------------------------------------------------------------- |
| **`SearchCondition`** (leaf)      | Tests one field on one job row — e.g. `title = "Software Engineer"`.         |
| **`SearchConditionGroup`** (node) | Combines conditions with `and` or `or`. Groups can nest inside other groups. |

<Warning>
  **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`.
</Warning>

<Info>
  **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").
</Info>

<Tip>
  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](/job-docs/search/examples#companies-indexing-both-software-engineers-and-account-executives).
</Tip>

### Single condition

```json theme={"theme":"vitesse-black"}
{
    "filters": {
        "field": "job_details.category",
        "type": "=",
        "value": "Engineering"
    }
}
```

### AND / OR group

```json theme={"theme":"vitesse-black"}
{
    "filters": {
        "op": "and",
        "conditions": [
            {
                "field": "company.basic_info.company_id",
                "type": "=",
                "value": 631394
            },
            {
                "field": "job_details.category",
                "type": "=",
                "value": "Engineering"
            },
            {
                "field": "metadata.date_added",
                "type": "=>",
                "value": "2025-01-01"
            }
        ]
    }
}
```

### Array-field filters and grouping

<AccordionGroup>
  <Accordion title="Filtering on array fields">
    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:

    ```json theme={"theme":"vitesse-black"}
    { "field": "company.basic_info.industries", "type": "=", "value": "Technology, Information and Internet" }
    ```

    This matches any company whose `industries` array contains that
    exact string. Use `(.)` to match words within any element.
  </Accordion>

  <Accordion title="Grouping by array fields">
    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.
  </Accordion>
</AccordionGroup>

***

## Filter operators

Use the table below to pick the right `type` for each condition. Every
operator works on indexed fields only.

| Operator | `value` shape                  | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| -------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `=`      | scalar (string/number/boolean) | Exact match.                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `!=`     | scalar                         | Not equal.                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `<`      | scalar (numeric or ISO date)   | Less than.                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `=<`     | scalar (numeric or ISO date)   | Less than or equal. **Not** `<=`.                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `>`      | scalar (numeric or ISO date)   | Greater than.                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `=>`     | scalar (numeric or ISO date)   | Greater than or equal. **Not** `>=`.                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `in`     | array of scalars               | Field value is any entry in the array.                                                                                                                                                                                                                                                                                                                                                                                                                |
| `not_in` | array of scalars               | Field value is none of the entries in the array.                                                                                                                                                                                                                                                                                                                                                                                                      |
| `(.)`    | string                         | **Case-insensitive all-words match.** Every word in the query must appear somewhere in the field, but **not necessarily next to each other or in the same order**. `"Software Engineer"` matches `"Software Engineer"`, `"Software Development Engineer"`, and `"Engineer, Software Systems"`. A single word like `"engineer"` also matches `"Engineering Manager"`. Great for broad keyword hunting in `job_details.title` or `content.description`. |
| `[.]`    | string                         | **Case-insensitive exact-phrase match.** The words must appear **contiguously and in order**. `"Software Engineer"` matches `"Senior Software Engineer"` but **not** `"Software Development Engineer"` (extra word in between) and **not** `"Engineer Software"` (wrong order). Use `[.]` when you need precision over recall.                                                                                                                        |

<Warning>
  **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` are currently not implemented — request the field via `fields` and filter for null presence client-side.
</Warning>

***

## 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](#field-reference) below.

<Note>
  **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](#jobs-ids-a-quick-map).
</Note>

<Tabs>
  <Tab title="Job details">
    | Field                        | Example                                                |
    | ---------------------------- | ------------------------------------------------------ |
    | `job_details.title`          | `"Software Engineer"`                                  |
    | `job_details.category`       | `"Engineering"`, `"Sales"`, `"Operations"`, `"Others"` |
    | `job_details.workplace_type` | `"Remote"`, `"Hybrid"`, `"On-site"`, `""`              |
    | `job_details.reposted_job`   | `true` / `false`                                       |
    | `job_details.url`            | `"https://www.linkedin.com/jobs/view/4398377738"`      |
  </Tab>

  <Tab title="Company basic info">
    | Field                                        | Example                                    |
    | -------------------------------------------- | ------------------------------------------ |
    | `company.basic_info.company_id`              | `631394`                                   |
    | `company.basic_info.name`                    | `"Stripe"`                                 |
    | `company.basic_info.primary_domain`          | `"stripe.com"`                             |
    | `company.basic_info.professional_network_id` | `"2135371"`                                |
    | `company.basic_info.industries`              | `["Technology, Information and Internet"]` |
  </Tab>

  <Tab title="Company firmographics">
    | Field                                       | Example        |
    | ------------------------------------------- | -------------- |
    | `company.headcount.total`                   | `14522`        |
    | `company.headcount.range`                   | `"5001-10000"` |
    | `company.followers.count`                   | `1335688`      |
    | `company.revenue.estimated.lower_bound_usd` | `500000000`    |
  </Tab>

  <Tab title="Location">
    | Field               | Example                            |
    | ------------------- | ---------------------------------- |
    | `location.raw`      | `"Melbourne, Victoria, Australia"` |
    | `location.country`  | `"Australia"`                      |
    | `location.state`    | `"Victoria"`                       |
    | `location.district` | `"Southbank"`                      |
    | `location.city`     | `"Melbourne"`                      |
  </Tab>

  <Tab title="Content, metadata, IDs">
    | Field                   | Example                    |
    | ----------------------- | -------------------------- |
    | `content.description`   | Full job description text. |
    | `crustdata_job_id`      | `41053563`                 |
    | `metadata.date_added`   | `"2026-04-07T11:37:29"`    |
    | `metadata.date_updated` | `"2026-04-08T00:00:00"`    |
  </Tab>
</Tabs>

<Note>
  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.
</Note>

***

## 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

<Note>
  The code fence below uses `jsonc` because it includes inline `//` comments
  for annotation. Strip the comments before sending it to a strict JSON
  parser.
</Note>

```jsonc theme={"theme":"vitesse-black"}
{
    "crustdata_job_id": 41053563, // stable job id (use as dedupe key)
    "job_details": {
        "job_id": 41053563, // mirrors crustdata_job_id
        "title": "Integration Engineer (AUNZ)",
        "category": "Engineering",
        "workplace_type": "",
        "url": "https://www.linkedin.com/jobs/view/4398377738",
        "reposted_job": false,
        "number_of_openings": 1,
    },
    "company": {
        "basic_info": {
            "crustdata_company_id": 631394, // filter with company.basic_info.company_id
            "name": "Stripe",
            "primary_domain": "stripe.com",
            "website": "https://stripe.com",
            "professional_network_id": "2135371",
            "industries": ["Technology, Information and Internet"],
        },
        "locations": {
            "country": "USA",
            "state": "California",
            "city": "South San Francisco",
            "street_address": "354 Oyster Point Blvd, South San Francisco, California, United States",
        },
        "headcount": {
            "total": 7234,
            "range": "5001-10000",
            "largest_headcount_country": "USA",
        },
        "followers": { "count": 1335688 },
        "revenue": {
            "estimated": {
                "lower_bound_usd": 500000000,
                "upper_bound_usd": 1000000000,
            },
            "public_markets": null,
            "acquisition_status": "",
        },
        "funding": {
            "total_investment_usd": 9440247725.0,
            "valuation_usd": 50000000000.0,
            "last_fundraise_date": "2026-03-09T00:00:00",
            "last_round_type": "secondary_market",
            "num_funding_rounds": 23,
            "investors": [
                "Sequoia Capital",
                "Andreessen Horowitz",
                "Founders Fund",
            ],
        },
        "competitors": {
            "websites": ["https://plaid.com", "https://paystack.com"],
        },
    },
    "location": {
        "raw": "Melbourne, Victoria, Australia",
        "city": "Melbourne",
        "district": null,
        "state": "Victoria",
        "country": "Australia",
        "pincode": null,
    },
    "content": {
        "description": "Stripe is a financial infrastructure platform for businesses...",
    },
    "metadata": {
        "date_added": "2026-04-07T11:37:29",
        "date_updated": "2026-04-08T00:00:00",
    },
}
```

<Tip>
  **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.
</Tip>

### Jobs IDs: a quick map

| ID                                        | Lives on                         | Purpose                                                                                                    |
| ----------------------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `crustdata_job_id`                        | Top-level on each `Job`          | Crustdata job identifier. Use it as your dedupe key in your own store.                                     |
| `job_details.job_id`                      | Inside `Job.job_details`         | Secondary job identifier. It currently mirrors `crustdata_job_id` and is kept for backwards compatibility. |
| `company.basic_info.crustdata_company_id` | Inside `Job.company.basic_info`  | Crustdata company identifier returned on each row.                                                         |
| `company.basic_info.company_id`           | Search filter / aggregation path | Indexed alias for the same company identifier. Use this in `filters.field` and `aggregations.field`.       |

### 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:

| Bucket metadata key      | Equivalent `Job` value                       | Notes                                                                                               |
| ------------------------ | -------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `company_name`           | `company.basic_info.name`                    | Plain company name.                                                                                 |
| `company_website_domain` | `company.basic_info.primary_domain`          | Primary website domain.                                                                             |
| `linkedin_id`            | `company.basic_info.professional_network_id` | Public-profile identifier returned only inside aggregation buckets.                                 |
| `crustdata_company_id`   | `company.basic_info.crustdata_company_id`    | Crustdata company id. Defined in the spec as nullable; the bucket `key` already carries this value. |

### Job identifiers

| Path                 | Type    | Filter | Sort | Group | Return | Example    |
| -------------------- | ------- | ------ | ---- | ----- | ------ | ---------- |
| `crustdata_job_id`   | integer | ✅      | —    | —     | ✅      | `41053563` |
| `job_details.job_id` | integer | —      | —    | —     | ✅      | `41053563` |

### Job details (`job_details.*`)

| Path                             | Type    | Filter | Sort | Group | Return | Example                                           |
| -------------------------------- | ------- | ------ | ---- | ----- | ------ | ------------------------------------------------- |
| `job_details.title`              | string  | ✅      | —    | ✅     | ✅      | `"Software Engineer"`                             |
| `job_details.category`           | string  | ✅      | —    | ✅     | ✅      | `"Engineering"`                                   |
| `job_details.workplace_type`     | string  | ✅      | —    | ✅     | ✅      | `"Remote"`, `"Hybrid"`, `"On-site"`, `""`         |
| `job_details.reposted_job`       | boolean | ✅      | —    | —     | ✅      | `false`                                           |
| `job_details.url`                | string  | ✅      | —    | —     | ✅      | `"https://www.linkedin.com/jobs/view/4398377738"` |
| `job_details.number_of_openings` | integer | —      | —    | —     | ✅      | `1`                                               |

### Company basic info (`company.basic_info.*`)

| Path                                         | Type      | Filter | Sort | Group | Return | Example                                    |
| -------------------------------------------- | --------- | ------ | ---- | ----- | ------ | ------------------------------------------ |
| `company.basic_info.company_id`              | integer   | ✅      | —    | ✅     | —      | `631394`                                   |
| `company.basic_info.crustdata_company_id`    | integer   | —      | —    | —     | ✅      | `631394`                                   |
| `company.basic_info.name`                    | string    | ✅      | —    | —     | ✅      | `"Stripe"`                                 |
| `company.basic_info.primary_domain`          | string    | ✅      | —    | ✅     | ✅      | `"stripe.com"`                             |
| `company.basic_info.website`                 | string    | —      | —    | —     | ✅      | `"https://stripe.com"`                     |
| `company.basic_info.professional_network_id` | string    | ✅      | —    | —     | ✅      | `"2135371"`                                |
| `company.basic_info.industries`              | string\[] | ✅      | —    | ✅     | ✅      | `["Technology, Information and Internet"]` |

<Note>
  `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`.
</Note>

### Company firmographics

#### Headcount (`company.headcount.*`)

| Path                                          | Type    | Filter | Sort | Group | Return | Example        |
| --------------------------------------------- | ------- | ------ | ---- | ----- | ------ | -------------- |
| `company.headcount.total`                     | integer | ✅      | ✅    | —     | ✅      | `14522`        |
| `company.headcount.range`                     | string  | ✅      | —    | ✅     | ✅      | `"5001-10000"` |
| `company.headcount.largest_headcount_country` | string  | —      | —    | —     | ✅      | `"USA"`        |

#### Followers (`company.followers.*`)

| Path                      | Type    | Filter | Sort | Group | Return | Example   |
| ------------------------- | ------- | ------ | ---- | ----- | ------ | --------- |
| `company.followers.count` | integer | ✅      | ✅    | —     | ✅      | `1335688` |

#### Revenue (`company.revenue.*`)

| Path                                             | Type      | Filter | Sort | Group | Return | Example      |
| ------------------------------------------------ | --------- | ------ | ---- | ----- | ------ | ------------ |
| `company.revenue.estimated.lower_bound_usd`      | integer   | ✅      | ✅    | —     | ✅      | `500000000`  |
| `company.revenue.estimated.upper_bound_usd`      | integer   | ✅      | ✅    | —     | ✅      | `1000000000` |
| `company.revenue.acquisition_status`             | string    | ✅      | —    | —     | ✅      | `""`         |
| `company.revenue.public_markets.stock_symbols`   | string\[] | —      | —    | —     | ✅      | `["STRIPE"]` |
| `company.revenue.public_markets.fiscal_year_end` | string    | —      | —    | —     | ✅      | `""`         |

#### Funding (`company.funding.*`)

| Path                                   | Type              | Filter | Sort | Group | Return | Example                 |
| -------------------------------------- | ----------------- | ------ | ---- | ----- | ------ | ----------------------- |
| `company.funding.total_investment_usd` | number            | ✅      | ✅    | —     | ✅      | `9440247725.0`          |
| `company.funding.valuation_usd`        | number            | ✅      | ✅    | —     | ✅      | `50000000000.0`         |
| `company.funding.last_fundraise_date`  | string (ISO 8601) | ✅      | ✅    | —     | ✅      | `"2026-03-09T00:00:00"` |
| `company.funding.last_round_type`      | string            | ✅      | —    | ✅     | ✅      | `"secondary_market"`    |
| `company.funding.num_funding_rounds`   | integer           | ✅      | ✅    | —     | ✅      | `23`                    |
| `company.funding.investors`            | string\[]         | —      | —    | —     | ✅      | `["Sequoia Capital"]`   |

#### Competitors and company locations

| Path                               | Type      | Filter | Sort | Group | Return | Example                        |
| ---------------------------------- | --------- | ------ | ---- | ----- | ------ | ------------------------------ |
| `company.competitors.websites`     | string\[] | —      | —    | —     | ✅      | `["https://plaid.com"]`        |
| `company.locations.country`        | string    | ✅      | —    | ✅     | ✅      | `"USA"`                        |
| `company.locations.state`          | string    | —      | —    | —     | ✅      | `"California"`                 |
| `company.locations.city`           | string    | —      | —    | —     | ✅      | `"South San Francisco"`        |
| `company.locations.street_address` | string    | —      | —    | —     | ✅      | `"354 Oyster Point Blvd, ..."` |

### Job location (`location.*`)

| Path                | Type   | Filter | Sort | Group | Return | Example                            |
| ------------------- | ------ | ------ | ---- | ----- | ------ | ---------------------------------- |
| `location.raw`      | string | ✅      | —    | —     | ✅      | `"Melbourne, Victoria, Australia"` |
| `location.city`     | string | ✅      | —    | —     | ✅      | `"Melbourne"`                      |
| `location.district` | string | ✅      | —    | —     | ✅      | `"Southbank"`                      |
| `location.state`    | string | ✅      | —    | —     | ✅      | `"Victoria"`                       |
| `location.country`  | string | ✅      | —    | ✅     | ✅      | `"Australia"`                      |
| `location.pincode`  | string | ✅      | —    | —     | ✅      | `"3006"`                           |

<Note>
  **Country value normalization.** `location.country` can appear as full names
  (`"United States"`), ISO-style short forms (`"USA"`), and occasional
  variants (`"United States of America"`). When filtering by country, either
  match multiple variants with `in` or pre-discover the exact indexed values
  by running a `group_by` on `location.country`.
</Note>

### Content (`content.*`)

| Path                  | Type   | Filter | Sort | Group | Return | Example                                     |
| --------------------- | ------ | ------ | ---- | ----- | ------ | ------------------------------------------- |
| `content.description` | string | ✅      | —    | —     | ✅      | `"Stripe is a financial infrastructure..."` |

<Tip>
  Use the `(.)` operator on `content.description` to find listings by
  technology, skill, or keyword.
</Tip>

### Metadata (`metadata.*`)

| Path                    | Type              | Filter | Sort | Group | Return | Example                 |
| ----------------------- | ----------------- | ------ | ---- | ----- | ------ | ----------------------- |
| `metadata.date_added`   | string (ISO 8601) | ✅      | ✅    | —     | ✅      | `"2026-04-07T11:37:29"` |
| `metadata.date_updated` | string (ISO 8601) | ✅      | ✅    | —     | ✅      | `"2026-04-08T00:00:00"` |

### 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 objects** — `company.funding`, `company.revenue`, and `company.competitors` are often missing for smaller or private companies.
* **`is_null` / `is_not_null` operators are currently not implemented** — request the field via `fields` and filter for null presence client-side.

***

## Errors

| Status | `error.type`      | Meaning                                                                                                                                                                   |
| ------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `invalid_request` | Request body failed validation — `limit` out of range, a missing required key, or an unrecognized parameter (for example sending `column` instead of `field`).            |
| `401`  | `unauthorized`    | The `Authorization` header is missing, malformed, or contains an invalid API key.                                                                                         |
| `500`  | `internal_error`  | The query could not be executed — including when a filter or aggregation references an unsupported field. Also covers transient server errors; retry after a short delay. |

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

<CodeGroup>
  ```json 400 — limit out of range theme={"theme":"vitesse-black"}
  {
      "error": {
          "type": "invalid_request",
          "message": "'limit' must be at most 1000. Got 5000.",
          "metadata": [
              {
                  "field": "limit",
                  "type": "less_than_equal",
                  "message": "'limit' must be at most 1000. Got 5000."
              }
          ]
      }
  }
  ```

  ```json 400 — column used instead of field theme={"theme":"vitesse-black"}
  {
      "error": {
          "type": "invalid_request",
          "message": "'aggregations.0.column' is not a valid parameter.",
          "metadata": [
              {
                  "field": "aggregations.0.column",
                  "type": "extra_forbidden",
                  "message": "'aggregations.0.column' is not a valid parameter."
              }
          ]
      }
  }
  ```

  ```json 401 — Invalid API key theme={"theme":"vitesse-black"}
  {
      "error": {
          "type": "unauthorized",
          "message": "Invalid API key in request.",
          "metadata": []
      }
  }
  ```

  ```json 500 — Unsupported filter field theme={"theme":"vitesse-black"}
  {
      "error": {
          "type": "internal_error",
          "message": "Unsupported columns in conditions: ['invalid_field']",
          "metadata": []
      }
  }
  ```

  ```json 500 — Unsupported group_by field theme={"theme":"vitesse-black"}
  {
      "error": {
          "type": "internal_error",
          "message": "Unsupported aggregation field: 'company.basic_info.name'. Supported: 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.title, job_details.workplace_type, location.country",
          "metadata": []
      }
  }
  ```
</CodeGroup>

## Pagination & sorting

How to paginate, sort, select fields, and aggregate results in
[Search Jobs](/job-docs/search/introduction).

For worked examples, see [Examples](/job-docs/search/examples). For filter
grammar, operators, and the full field catalog, see
[Reference](/job-docs/search/reference).

<Note>
  Replace `YOUR_API_KEY` in each example with your actual API key. All
  requests require the `x-api-version: 2025-11-01` header.
</Note>

***

## 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.

```json theme={"theme":"vitesse-black"}
{
    "sorts": [
        { "field": "metadata.date_added", "order": "desc" },
        { "field": "company.headcount.total", "order": "desc" }
    ]
}
```

<Warning>
  **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`.
</Warning>

### Sortable fields

The following indexed fields are verified sortable:

* `metadata.date_added`
* `metadata.date_updated`
* `company.headcount.total`
* `company.followers.count`
* `company.revenue.estimated.lower_bound_usd`
* `company.revenue.estimated.upper_bound_usd`
* `company.funding.total_investment_usd`
* `company.funding.valuation_usd`
* `company.funding.last_fundraise_date`
* `company.funding.num_funding_rounds`

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" }`
* **Highest-funded companies first** — `{ "field": "company.funding.total_investment_usd", "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`.

<Steps>
  <Step title="Fetch the first page">
    Omit `cursor` and set `limit` to your page size (max `1000`).
  </Step>

  <Step title="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.
  </Step>

  <Step title="Stop when `next_cursor` is null">
    A `null` cursor means you've reached the end of the result set.
  </Step>
</Steps>

### Consistency between pages

<Note>
  **Current platform behavior — 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.
</Note>

### Dataset freshness and lifecycle

<Note>
  **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`.
</Note>

### Date filter semantics

<Note>
  **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.
</Note>

### Fetch page 2

```json theme={"theme":"vitesse-black"}
{
    "filters": {
        "field": "company.basic_info.company_id",
        "type": "=",
        "value": 631394
    },
    "fields": ["job_details.title"],
    "limit": 1,
    "cursor": "H4sIANBG1mkC_xXMOQ4CMQxA0auMUk9hx3YScxWERs6CpkBEzFIgxN0J1W-e_se9zra9l9X21V0mx0kjMgvdq4dYPZGgclOTlLEZl2bJG_lcdVSqFa-okDlwo1qbmye39-0YryvGKBIwsQLAPDGCkAS6DXL0wx5L6efzL2MC_P4A250zQYoAAAA="
}
```

***

## 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.

```json theme={"theme":"vitesse-black"}
{
    "fields": [
        "job_details.title",
        "job_details.url",
        "company.basic_info.name",
        "company.basic_info.primary_domain",
        "location.raw",
        "metadata.date_added"
    ]
}
```

<Tip>
  **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"]`.
</Tip>

***

## 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

| Field   | Type          | Required                | Description                                                                           |
| ------- | ------------- | ----------------------- | ------------------------------------------------------------------------------------- |
| `type`  | string (enum) | **Yes**                 | `"count"` for a simple total, `"group_by"` to bucket by `field`.                      |
| `field` | string        | Required for `group_by` | Dot-path to group by. Must be in the [Groupable fields](#groupable-fields) allowlist. |
| `agg`   | string (enum) | Required for `group_by` | Sub-aggregation inside each bucket. Currently only `"count"` is supported.            |
| `size`  | integer       | No (default `100`)      | Maximum number of buckets to return. Min `1`, max `1000`.                             |

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](/job-docs/search/reference#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

<CodeGroup>
  ```bash curl theme={"theme":"vitesse-black"}
  curl --request POST \
    --url https://api.crustdata.com/job/search \
    --header 'authorization: Bearer YOUR_API_KEY' \
    --header 'content-type: application/json' \
    --header 'x-api-version: 2025-11-01' \
    --data '{
      "filters": { "field": "job_details.category", "type": "=", "value": "Engineering" },
      "limit": 0,
      "aggregations": [ { "type": "count" } ]
    }'
  ```

  ```json Response theme={"theme":"vitesse-black"}
  {
      "job_listings": [],
      "next_cursor": null,
      "total_count": 4448237,
      "aggregations": [{ "type": "count", "field": null, "value": 4448237 }]
  }
  ```
</CodeGroup>

### Top companies indexing "Software Engineer" listings (bounded window)

<CodeGroup>
  ```bash curl theme={"theme":"vitesse-black"}
  curl --request POST \
    --url https://api.crustdata.com/job/search \
    --header 'authorization: Bearer YOUR_API_KEY' \
    --header 'content-type: application/json' \
    --header 'x-api-version: 2025-11-01' \
    --data '{
      "filters": {
        "op": "and",
        "conditions": [
          { "field": "job_details.title",   "type": "=",  "value": "Software Engineer" },
          { "field": "metadata.date_added", "type": "=>", "value": "2025-01-01" },
          { "field": "metadata.date_added", "type": "<",  "value": "2026-01-01" }
        ]
      },
      "limit": 0,
      "aggregations": [
        {
          "type": "group_by",
          "field": "company.basic_info.company_id",
          "agg": "count",
          "size": 5
        }
      ]
    }'
  ```

  ```json Response theme={"theme":"vitesse-black"}
  {
      "job_listings": [],
      "next_cursor": null,
      "total_count": 25748,
      "aggregations": [
          {
              "type": "group_by",
              "field": "company.basic_info.company_id",
              "buckets": [
                  {
                      "key": 3674630,
                      "count": 836,
                      "metadata": {
                          "company_name": "Bending Spoons",
                          "company_website_domain": "bndspn.com",
                          "linkedin_id": "3175130"
                      }
                  },
                  {
                      "key": 821755,
                      "count": 794,
                      "metadata": {
                          "company_name": "Jobs via Dice",
                          "company_website_domain": "dice.com",
                          "linkedin_id": "104085107"
                      }
                  },
                  {
                      "key": 899641,
                      "count": 490,
                      "metadata": {
                          "company_name": "Lensa",
                          "company_website_domain": "lensa.com",
                          "linkedin_id": "5192530"
                      }
                  },
                  {
                      "key": 2110301,
                      "count": 438,
                      "metadata": {
                          "company_name": "Microsoft",
                          "company_website_domain": "microsoft.com",
                          "linkedin_id": "1035"
                      }
                  },
                  {
                      "key": 2676775,
                      "count": 379,
                      "metadata": {
                          "company_name": "Goliath Partners",
                          "company_website_domain": "goliathpartners.com",
                          "linkedin_id": "92699700"
                      }
                  }
              ]
          }
      ]
  }
  ```
</CodeGroup>

### 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.title`
* `job_details.workplace_type`
* `location.country`

Sending any other field returns `500` with `Unsupported aggregation
field: '...'. Supported: ...`.

***

## What's next

* [Search Jobs](/job-docs/search/introduction) — back to the main Search page.
* [Examples](/job-docs/search/examples) — SDR/BDR keyword hunting, mid-market filtering, funding-triggered queries, and aggregations.
* [Pagination & sorting](#pagination--sorting) — sorting, pagination, field selection, and aggregations.
* [OpenAPI reference](/openapi-specs/2025-11-01/introduction) — the formal schema for every request, response, and error.
