API reference
Query Spec
One grammar searches all three datasets. It is a small boolean tree of field conditions, and the same request shape works against people, jobs, and companies.
The shape of a query
Every structured search sends a where tree and an optional page size. The tree is built from two things: composers, which combine conditions, and leaves, which test one field.
{
"where": {"all": [
{"field": "<name>", "<operator>": <value>}
]},
"size": 25
}A leaf names one field and applies one operator to it. Everything else in the grammar is a way of arranging leaves.
A query must carry at least one condition. An empty tree is refused with At least one search condition is required rather than returning the whole corpus.
Composing conditions
- Composer
- all
- Takes
- An array of nodes
- Matches when
- Every node matches. This is the usual outer wrapper
- Composer
- any
- Takes
- An array of nodes
- Matches when
- At least one node matches
- Composer
- not
- Takes
- A single node, or an array
- Matches when
- The node does not match. An array negates all of it
Composers nest, so an all can contain an any that contains a not. This example asks for a company in the United States that is either mid-sized or well funded, and is not publicly listed.
/v1/companies/query{
"where": {"all": [
{"field": "hq_country", "match": "United States"},
{"any": [
{"field": "employees_count", "gte": 200},
{"field": "last_funding_round_amount_raised", "gte": 50000000}
]},
{"not": {"field": "type", "eq": "Public Company"}}
]},
"size": 50
}Operators
- Operator
- eq
- Takes
- One value
- Means
- Exact match on the whole value
- Operator
- in
- Takes
- A non-empty array
- Means
- Matches any one of the listed values
- Operator
- match
- Takes
- Text
- Means
- Analyzed text match. Every word must be present
- Operator
- gte
- Takes
- A number, or a date on a date field
- Means
- At or above
- Operator
- lte
- Takes
- A number, or a date on a date field
- Means
- At or below
- Operator
- exists
- Takes
- true
- Means
- The field is present on the record
One operator per leaf
all of two leaves over the same field, not gte and lte in one node. This is the mistake nearly every caller makes first, and it is refused with Each field condition takes exactly one operatorrather than silently applying one half of the range.employees_count below is a company field. Field names are not shared between the three datasets, so a leaf only means anything against the endpoint that publishes its field.
// Wrong. Two operators in one leaf.
{"field": "employees_count", "gte": 50, "lte": 500}
// Right. Two leaves, combined.
{"all": [
{"field": "employees_count", "gte": 50},
{"field": "employees_count", "lte": 500}
]}Operators are also matched to the kind of field. Numeric and date fields accept gte, lte, and exists only; text and category fields accept eq, in, match, and exists. Asking a numeric field for eq, or passing "50" as a string where a number is expected, is refused rather than reinterpreted. Each dataset page lists the operators its fields accept.
exists has one form. To ask for records where a field is absent, wrap the leaf: {"not": {"field": "salary_min", "exists": true}}.
Exact fields are case-sensitive
A text field matches text, where every word in your value has to appear. An exact field compares the whole stored value, character for character, so "director" matches nothing where "Director" matches.
The one mistake that raises no error
Several fields draw from a fixed list, and the lists live with the fields rather than here: People, Jobs and Companies each carry the values for their own. One copy per value, on the page you were already reading to find the field.
Several of those values contain commas — Oil, Gas, and Mining is one value, not three — which is the other half of why they are shown in a table there rather than run together in a sentence here.
Asking a yes/no field for no
true means the same thing on all six of them. false does not, because a field that is only written when it is true has no false to match: it is absent instead, and absent is not a value you can ask for with eq.
- Field
- is_working, is_decision_maker, is_current
- How to ask for no
- false. Both answers are stored, so both can be asked for
- Field
- is_studying, is_b2b
- How to ask for no
- not, wrapping the positive: the field is absent rather than false, so eq false matches nothing
- Field
- application_active
- How to ask for no
- Nothing to ask. Every job in the searchable index is active, so this condition removes no results today
/v1/companies/query// Matches nothing: there is no stored false to match.
{"where": {"field": "is_b2b", "eq": false}}
// The companies that are not B2B.
{"where": {"not": {"field": "is_b2b", "eq": true}}}Why this is worth a section
Relative dates
Date fields take an absolute YYYY-MM-DD or a relative value that the server resolves to an absolute day before the query runs. That means a saved query keeps meaning "the last 30 days" instead of freezing on the day it was written.
- Form
- now
- Example
- now
- Maximum offset
- Not applicable
- Form
- now-<n>d
- Example
- now-30d
- Maximum offset
- 7300 days
- Form
- now-<n>m
- Example
- now-6m
- Maximum offset
- 240 months
- Form
- now-<n>y
- Example
- now-5y
- Maximum offset
- 20 years
/v1/jobs/query{"where": {"all": [
{"field": "posted", "gte": "now-30d"},
{"field": "employment_type", "match": "Contract"}
]}}A relative value on a field that is not a date is refused. The date fields are marked as such in each dataset's field table.
Same-record scopes
A profile holds many jobs and many degrees. Without a scope, two conditions can be satisfied by two different records: one job at Google, and a separate job as a director somewhere else. A scope forces both to land on the same record.
// "Was a director at Google": both conditions, one job.
{"where": {"has_experience": {"all": [
{"field": "company_name", "match": "Google"},
{"field": "level", "eq": "Director"}
]}}}
// "Worked at Google, and was a director somewhere":
// two conditions, possibly two different jobs.
{"where": {"all": [
{"field": "company_name", "match": "Google"},
{"field": "level", "eq": "Director"}
]}}- Scope
- has_experience
- Groups
- One job: employer, title, level, dates, and workplace location
- Available on
- People
- Scope
- has_education
- Groups
- One degree: institution, major, level, and years
- Available on
- People
- Scope
- has_language
- Groups
- One language entry
- Available on
- People
People are the only entity with scopes. A job document is one job and a company document is one company, so there is nothing to group. Scopes also do not nest inside one another, and each scope accepts only the fields that belong to it.
Paging with after and next
A search response includes next while more results remain. Pass that value back as after, with the same where tree, to get the following page. On the last page next is absent rather than empty, so its absence is how you know you are done.
/v1/jobs/query// Page 1 response
{"data": {"job_ids": ["Jb71xKcAoP"], "next": "WzE3MjQwMDAwMDAsIkpiNzF4S2NBb1AiXQ"}}
// Page 2 request
{"where": {"all": [{"field": "title", "match": "data engineer"}]},
"size": 100,
"after": "WzE3MjQwMDAwMDAsIkpiNzF4S2NBb1AiXQ"}Pages are not a snapshot
Limits
- Limit
- Nesting depth
- Value
- 6
- What happens past it
- The query is refused before it runs
- Limit
- Conditions per query
- Value
- 64
- What happens past it
- The query is refused before it runs
- Limit
- Results per page
- Value
- 100 by default, 10,000 maximum
- What happens past it
- Values above the maximum are rejected
- Limit
- IDs per detail request
- Value
- 100
- What happens past it
- Split the IDs across several calls
A field name that is not in the dataset's list is refused. The three datasets do not share one vocabulary, so check the name against the page for the endpoint you are calling. A translation failure costs nothing: the query is checked before any Credit is charged.
Where the field names live
The grammar is shared; the vocabulary is not. Each dataset publishes its own field list with the operators each field accepts, and a name from one dataset is not valid in another.
What a search can filter on and what a detail response can return are two different lists. A field may be returnable without being queryable, and the query vocabulary is deliberately the smaller of the two.