Skip to main content

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.

POST /data_lab/job_listings/Table/
POST /job/search
This page lists every behavioral and contract change between the legacy job-listings dataset-query endpoint and the current job-search endpoint. Use it as a side-by-side reference when porting an integration; each section gives the old shape, the new shape, and the smallest edit that closes the gap.
TopicLegacyCurrent
PathPOST /data_lab/job_listings/Table/POST /job/search
MethodPOST with a JSON bodyPOST with a JSON body
AuthAuthorization: Token <key>Authorization: Bearer <key>
Version(not required)x-api-version: 2025-11-01 (required)
Base URLhttps://api.crustdata.comhttps://api.crustdata.com
Three header / shape changes are required. The path and body grammar moved to a job-specific endpoint, the authorization scheme moved from Token <key> to Bearer <key>, and the x-api-version: 2025-11-01 header is now required on every call. Calls missing any of these are rejected.
The legacy endpoint was a generic dataset-query API parameterized by a dataset_id (job_listings) and a view_type (Table). The new endpoint is a purpose-built job-search API — there is no dataset_id or view_type in the URL, and the request body no longer carries a dataset object. For real-time retrieval of currently-open roles for a single company, use Live Job Search instead of the legacy sync_from_source=true flag.

1. Request body — top-level keys

The request body moved from a generic-dataset shape (with dataset, tickers, functions, groups, sync_from_source, background_task, offset) to a job-specific shape with cursor pagination and structured aggregations.
Legacy keyCurrent keyNotes
dataset(removed)The endpoint path identifies the dataset. Drop { "name": "job_listings", "id": "joblisting" } from the body.
filtersfiltersGrammar changed — see Filter grammar below. The key is unchanged, but columnfield.
sortssortsSame array shape. { column, order }. Field names changed — see Field-name mapping.
countlimitRemoved count as an alias. Use limit exclusively.
limitlimitUnchanged key. Default 20, max 1000 (up from 100).
offsetcursorPagination moved from numeric offset to opaque cursor — see Pagination.
groupsaggregations[].type=group_byGroup-by is now expressed inside aggregations — see Aggregations.
aggregationsaggregationsSame key. Shape changed — see Aggregations.
functions(removed)Custom expression functions are not exposed on the new endpoint.
tickers(removed)Ticker-based scoping is not used; use a company.basic_info.company_id or company.basic_info.primary_domain filter instead.
sync_from_source(removed — see callout)Real-time retrieval moved to a dedicated endpoint — see Removed features.
background_task(removed)Asynchronous bulk fetches are not part of the new endpoint.
(new)fieldsRequest a subset of response sections via dot-paths — see Field selection.

2. Filter grammar

The condition key changed from column to field. The and/or group shape is unchanged, and operators are unchanged. The list of filterable fields was narrowed to indexed fields only.
{
    "column": "company_id",
    "type": "in",
    "value": [631394, 631811]
}
Two changes per condition:
  1. Rename columnfield.
  2. Map the legacy column name to the new dot-path (see Field-name mapping).

Operators

Operators are unchanged. Both endpoints accept:
OperatorMeaning
=, !=Exact match / negation.
<, =<, >, =>Numeric or date comparison.
in, not_inSet membership. value must be an array.
(.)Case-insensitive substring match.
[.]Case-insensitive exact word/phrase match.

Group shape

The and / or group shape is unchanged:
{
    "op": "and",
    "conditions": [
        { "field": "company.basic_info.company_id", "type": "=", "value": 631394 },
        { "field": "job_details.category", "type": "=", "value": "Engineering" }
    ]
}
Nested groups are supported (mix and and or at any depth).

Filterable fields

The new endpoint restricts filters to indexed fields only. The filterable set is:
GroupFields
Job detailsjob_details.title, job_details.category, job_details.workplace_type, job_details.reposted_job, job_details.url
Company basic infocompany.basic_info.company_id, company.basic_info.name, company.basic_info.primary_domain, company.basic_info.professional_network_id, company.basic_info.industries
Company firmographicscompany.headcount.total, company.headcount.range, company.followers.count, company.revenue.estimated.lower_bound_usd
Locationlocation.raw, location.country, location.state, location.district, location.city
Contentcontent.description
Identifierscrustdata_job_id
Metadatametadata.date_added, metadata.date_updated
Filtering on any other column returns 400 with "Unsupported columns in conditions: [...]". Legacy integrations that relied on filtering by raw firmographic columns (e.g., linkedin_industries directly, or last_funding_round_type) should move those filters to the indexed names above or compose them on Company Search first.

3. Field-name mapping

The legacy endpoint returned a fields[] + rows[][] table where each column was identified by a flat api_name like title, company_name, linkedin_industries, or date_added. The current endpoint returns structured job objects with nested sections (job_details, company, location, content, metadata) — filter and sort references must use the new dot-paths.

Job details

Legacy columnCurrent field
titlejob_details.title
categoryjob_details.category
urljob_details.url
workplace_typejob_details.workplace_type
reposted_jobjob_details.reposted_job
number_of_openingsjob_details.number_of_openings
dataset_row_idcrustdata_job_id (top-level)

Job location

The legacy response carried a single flat city, location_text, country, state, and district columns (and several *_geocode foreign-key columns). The current response groups these under location with both parsed values and the raw posting string.
Legacy columnCurrent field
location_textlocation.raw
citylocation.city
statelocation.state
countrylocation.country
districtlocation.district
pincodelocation.pincode

Content

Legacy columnCurrent field
descriptioncontent.description

Metadata

Legacy columnCurrent field
date_addedmetadata.date_added
date_updatedmetadata.date_updated

Company firmographics

The legacy response interleaved flat company columns (company_id, company_name, company_website_domain, linkedin_industries, linkedin_headcount, linkedin_followers, crunchbase_*, gartner_*, glassdoor_*, …) into the same row as the job columns. The current response nests these under a single company object using the same section names as Company Enrich.
Legacy columnCurrent field
company_idcompany.basic_info.crustdata_company_id
company_namecompany.basic_info.name
company_website_domaincompany.basic_info.primary_domain
company_websitecompany.basic_info.website
linkedin_idcompany.basic_info.professional_network_id
linkedin_industriescompany.basic_info.industries[]
linkedin_headcountcompany.headcount.total
linkedin_headcount_range / employee_count_rangecompany.headcount.range
largest_headcount_countrycompany.headcount.largest_headcount_country
linkedin_followerscompany.followers.count
hq_countrycompany.locations.country
hq_statecompany.locations.state
hq_citycompany.locations.city
hq_street_addresscompany.locations.street_address
estimated_revenue_lower_bound_usdcompany.revenue.estimated.lower_bound_usd
estimated_revenue_higher_bound_usdcompany.revenue.estimated.upper_bound_usd
stock_symbolscompany.revenue.public_markets.stock_symbols
fiscal_year_endcompany.revenue.public_markets.fiscal_year_end
acquisition_statuscompany.revenue.acquisition_status
crunchbase_total_investment_usdcompany.funding.total_investment_usd
crunchbase_valuation_usdcompany.funding.valuation_usd
last_funding_round_datecompany.funding.last_fundraise_date
last_funding_round_typecompany.funding.last_round_type
num_funding_roundscompany.funding.num_funding_rounds
crunchbase_investorscompany.funding.investors[]
competitor_website_domainscompany.competitors.websites[]
For the full company-data field catalog, see Company Enrich reference.

4. Field selection

The legacy endpoint returned every column in the dataset by default. The new endpoint accepts an optional fields array — request only the sections you need to keep payloads small.
{
    "fields": [
        "job_details.title",
        "job_details.url",
        "company.basic_info.name",
        "location.raw",
        "metadata.date_added"
    ]
}
Valid top-level groups: crustdata_job_id, job_details, company, location, content, metadata. Use dot-notation for nested fields (company.headcount, company.basic_info.name). When fields is omitted, all available sections are returned.

5. Aggregations

Group-by has moved out of the top-level groups key and now lives inside aggregations. Each aggregation declares its type (count or group_by) explicitly.

Count

{
    "dataset": { "name": "job_listings", "id": "joblisting" },
    "filters": { "column": "category", "type": "=", "value": "Engineering" },
    "aggregations": [
        { "column": "id", "type": "count" }
    ],
    "limit": 0
}

Group-by

{
    "dataset": { "name": "job_listings", "id": "joblisting" },
    "filters": { "column": "title", "type": "=", "value": "Software Engineer" },
    "groups": [
        { "column": "company_id" }
    ],
    "aggregations": [
        { "column": "id", "type": "count" }
    ],
    "limit": 0
}
Set limit: 0 on the request when you only want aggregation output and no job rows. The response’s job_listings array will be empty and aggregations[] will carry the buckets.

Supported group-by columns

The set of columns you can group by is restricted on the new endpoint: company.basic_info.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. Grouping by any other column returns 400 with "Unsupported aggregation column: '...'".

Aggregation response shape

Aggregation results are returned in a typed aggregations[] array on the response:
{
    "job_listings": [],
    "next_cursor": null,
    "total_count": 30495,
    "aggregations": [
        {
            "type": "group_by",
            "column": "company.basic_info.company_id",
            "buckets": [
                {
                    "key": 821755,
                    "count": 1037,
                    "metadata": {
                        "company_name": "Jobs via Dice",
                        "company_website_domain": "dice.com"
                    }
                }
            ]
        }
    ]
}
For count aggregations the result includes a value field instead of buckets.

6. Removed features

sync_from_source (real-time retrieval)

The legacy endpoint accepted sync_from_source: true to bypass the indexed dataset and pull live job listings from the web for a single company. This flag has been removed from /job/search — the current endpoint serves the indexed dataset only. For real-time job retrieval, call the dedicated live endpoint instead — see Live Job Search. It accepts a single crustdata_company_id and returns up to 100 currently-open job listings from the web.

background_task

The legacy background_task: true flag enqueued an asynchronous bulk fetch of job listings for up to 10 companies, returning a task handle the caller could poll. This mode has been removed; build pagination around cursor-based requests on /job/search instead.

dataset and view_type

The legacy URL embedded a view_type (Table) and the body required a dataset object ({ "name": "job_listings", "id": "joblisting" }). Both have been removed — /job/search is purpose-built for job listings.

tickers

The legacy tickers array narrowed results to a set of stock or private tickers (e.g., "PRIVATE:STRIPE"). This input is not part of /job/search; filter by company.basic_info.company_id or company.basic_info.primary_domain instead.

functions

The legacy functions array allowed custom expression operations on columns. This input is not part of /job/search.

groups (top-level)

Group-by moved into aggregations[].type = group_by — see Aggregations.

offset

Numeric offset pagination is replaced by an opaque cursor — see Pagination.

count

count as an alias for limit is removed. Use limit exclusively.

compressed view (/data_lab/job_listings/Table/compressed)

The legacy server-side compressed-response variant has been removed. If you need a compact payload, request only the fields you need with the new fields array.

7. Pagination

Pagination moved from numeric offset (offset + limit) to opaque cursors. Each response carries a next_cursor value — pass it back as cursor on the next request to get the following page.
{
    "dataset": { "name": "job_listings", "id": "joblisting" },
    "filters": { "column": "company_id", "type": "=", "value": 631394 },
    "limit": 20,
    "offset": 0
}
Cursors encode the full query state (filter, sort, field selection). If you change any of those between page requests, drop the cursor and paginate from the beginning. next_cursor is null on the final page.

8. Response shape

The envelope shape changed. The legacy endpoint returned a { fields, rows } table where each row was a positional array of column values. The current endpoint returns a paginated { job_listings, next_cursor, total_count, aggregations } envelope where each entry is a structured Job object.

Envelope

{
    "fields": [
        { "api_name": "title", "type": "string" },
        { "api_name": "company_name", "type": "string" },
        { "api_name": "country", "type": "string" },
        { "api_name": "date_added", "type": "date" }
    ],
    "rows": [
        ["Integration Engineer", "Stripe", "Australia", "2026-04-07T11:37:29"]
    ],
    "total": 1676
}

Top-level response fields

FieldDescription
job_listingsArray of Job objects matching the filter for the current page. Empty when limit is 0 or when only aggregations were requested.
next_cursorOpaque cursor for the next page. null on the last page.
total_countTotal number of matching listings across all pages.
aggregationsAggregation results — present only when the request included an aggregations array.

Job object sections

SectionDescription
crustdata_job_idTop-level integer identifier for the listing.
job_detailsJob title, category, URL, workplace type, repost flag, number of openings.
companyHiring company’s firmographics: basic_info, locations, headcount, followers, revenue, funding, competitors.
locationJob’s advertised location: raw, city, state, country, district, pincode.
contentFull job description text under content.description.
metadataIndexing timestamps: date_added, date_updated.
For the full field catalog, see Job Search reference.

9. Error responses

The error envelope changed shape. Status codes are unchanged (400, 401, 403, 500).
{
    "error": "Invalid filter structure: column 'foo' not found"
}
401 continues to use a flat { "message": "Invalid API key in request" } shape — parse based on HTTP status. Common error cases:
StatusCause
400Unsupported columns in conditions: [...] — a filter references a non-indexed field.
400Unsupported aggregation column: '...'group_by references a column outside the allowed set.
400limit exceeds 1000 or is negative.
401Missing or invalid Authorization header.

10. End-to-end example

Fetching the first page of Engineering jobs at a single company, sorted by most recently added — written against both endpoints.
curl --request POST \
  --url 'https://api.crustdata.com/data_lab/job_listings/Table/' \
  --header 'authorization: Token YOUR_API_KEY' \
  --header 'content-type: application/json' \
  --data '{
    "dataset": { "name": "job_listings", "id": "joblisting" },
    "filters": {
      "op": "and",
      "conditions": [
        { "column": "company_id", "type": "=", "value": 631394 },
        { "column": "category", "type": "=", "value": "Engineering" }
      ]
    },
    "sorts": [ { "column": "date_added", "order": "desc" } ],
    "limit": 20,
    "offset": 0
  }'

Migration checklist

  • Switch from POST /data_lab/job_listings/Table/ to POST /job/search.
  • Change the Authorization scheme from Token to Bearer.
  • Add x-api-version: 2025-11-01 header to every request.
  • Drop the dataset object from the body — the path identifies the dataset.
  • Drop tickers, functions, background_task from the body.
  • Drop sync_from_source: true — for real-time retrieval, call Live Job Search instead.
  • Rename columnfield in every filter and sort condition.
  • Map legacy column names to the new dot-paths (see Field-name mapping).
  • Replace offset-based pagination with cursor from next_cursor.
  • Drop count as an alias for limit — use limit exclusively. Raise the default cap if needed: new max is 1000.
  • Move groups into aggregations[] with type: group_by. Add the required agg: count and optional size.
  • Wrap legacy aggregations: [{ column, type: count }] as aggregations: [{ type: count }] — drop the column for count.
  • Set limit: 0 when you only want aggregation output (and no job rows).
  • Update parsers for the new structured envelope { job_listings, next_cursor, total_count, aggregations } — the legacy { fields, rows } table shape is gone.
  • Update row parsers: each Job is a nested object with crustdata_job_id, job_details, company, location, content, metadata rather than a positional array.
  • Replace flat company_*, linkedin_*, crunchbase_* column reads with the new nested company.* paths (see Field-name mapping).
  • Replace flat city / state / country / location_text reads with location.{city,state,country,raw,district,pincode}.
  • Replace date_added / date_updated reads with metadata.date_added / metadata.date_updated.
  • Add a fields array if you only need a subset of sections.
  • Update error handlers for the new error.type / error.message envelope.
  • If you relied on /data_lab/job_listings/Table/compressed, request a slimmer payload via fields instead.

See also