Endpoints
Sort fields
Order a search explicitly — the sort clause on /v1/search, and the live catalogue of what each category can be ordered by.
/v1/sorts/{category}List the fields a search can be ordered by
Every sort field one category accepts, complete and unpaginated.
Complete on purpose: the largest set is 26, and a picker that has to page through a closed list to learn what is legal is the problem this route exists to remove.
Read applies_to before rendering. On company and person the same catalogue covers both axes — 13 fields are company-only, 8 person-only, and 5 work on either — and the engine refuses the wrong pairing.
Scope: dsp.search.read
Use it when you are building a sort control, or you need results in a specific order rather than the engine's. Outcome: the fields this category accepts, with display labels, value kinds and per-direction wording.
Two halves of one feature:
sortonPOST /v1/searchsays how to order.- This route says what can be ordered by.
Between them a client builds a sort control without hard-coding a list that drifts the moment the engine's registry does.
The sort clause on a search#
sort is an array of clauses, strongest key first, at most three:
{
"query": "fintech companies in berlin",
"category": "company",
"sort": [
{ "field": "employee_count", "direction": "desc" },
{ "field": "founded_year", "direction": "asc" }
],
"limit": 10
}directionisascordescand defaults todesc. Every field accepts both.- Omit
sortentirely to get the engine's own ordering — relevance when there is aqueryto be relevant to, and a quality ranking when there is not. That is the right default for most searches; sorting explicitly replaces relevance rather than refining it. sortneeds an explicitcategory. Omittingcategoryasks for auto-classification, and the sortable fields differ per category, so there would be nothing to validate the clause against. The pair is a422.- A repeated
fieldis a422— two orderings of the same key contradict each other rather than compose.
resolved_search.sort echoes the ordering that was actually applied, so a
response always says how it was ordered even when a cursor page carried the
clause rather than your request body.
Request#
Path parameters
categoryEntityCategoryrequiredWhich category's sort fields to list.
category is the path parameter, not in the table above: one of the nine
categories.
The answer is complete and unpaginated. The largest set is 26 fields, and a picker that has to page through a closed list to learn what is legal is the problem this route exists to remove.
Response#
categoryEntityCategory*What* to search, and what a returned row is. One parameter carries both, because they are the same fact: a search of patent returns patents. The alternative — a source/vertical selector beside a narrower category — lets a caller ask for patents from the people index, a combination that has no meaning and would need rejecting at runtime. A closed set that cannot express the contradiction is better than a validator that catches it. Values are exactly prism's own row types, so category in the request and results[].category in the response are always the same string. company and person are the GTM index; the rest are separate corpora with their own filters, freshness and coverage. Not everything applies everywhere: see SearchRequest, which rejects the GTM-only parameters rather than ignoring them. job_opening is a document corpus like patent/news_article/etc — a posting, not an entity — but its records carry a domain (the posting company's) all the same. It is deliberately not in GTM_CATEGORIES (that set is about the entity/document distinction, and a job posting is still a document), but it *is* in DOMAIN_FILTERABLE_CATEGORIES, which exists specifically to separate those two axes.
directionsarray of stringmetaResponseMetaPresent on every 2xx, including the ones that are not paginated.
as_ofstringcredential"api_key" | "session"default: api_keyWhich credential produced this response. session means the dashboard playground ran it as the signed-in user rather than as an API key — so it is billed to the user, subject to interactive rate limits, and carries no integration compatibility guarantee. Additive and defaulted, so it breaks no existing client.
credits_chargednumber | nullWhat this request cost, in credits, to at most three decimals. 0 when it cost nothing: a Basic or AI search that found nothing, rows this search already paid for (a cursor sent again, or a request repeated with the same Idempotency-Key on the same UTC day), or a test key. A request without a cursor or a repeated key is a new search, so keep the first page rather than asking for it again. null on a free endpoint.
environment"live" | "test"Echoed on every response so a developer can never mistake a test-key result for a live one.
request_idstringsortsarray of SortFieldInfoEmpty for a category that cannot be ordered explicitly. That is an answer: the engine still orders the results, it just does not take instruction.
applies_toarray of stringcompany, person, or both — populated for those two categories only, since a document has no such axis. Not advisory: sending a person-axis sort on a company search is refused. A picker that offers every field regardless of the resolved category will produce 422s.
default_directionstringWhat the engine would use if you named this field and no direction. Biggest-first for money and size, A-Z for names, longest-first for tenure.
definitionstring | nullWhat the field means, in words. Null where none is recorded.
direction_labelsDirection LabelsWhat each direction means for this field, keyed by direction: {"asc": "Shortest first", "desc": "Longest first"}. Render these rather than deriving words from kind — kind describes how the value is stored, not what ordering it means. tenure is the one that bites: it sorts on a start date, so a date-shaped label offers "Newest first" for the request that returns the longest-serving people.
directionsarray of stringAlways both, today.
fieldstringThe value to send as sort[].field.
groupstring | nullWhich section to list this field under: identity, location, professional, firmographic or funding. The same grouping the column catalogue uses, so a sort picker and a column picker file a field under the same heading. Null on a document category, which has no grouping.
kindstringnumber, date or text. Enough to choose a control; not a parser — the values themselves never come back through this route.
labelstringDisplay name. Matches the column's own label.
totalintegerThe example on the right is GET /v1/sorts/company, trimmed to one field.
Read applies_to before rendering
On company and person the same catalogue covers both axes: 13 fields
are company-only, 8 person-only, and 5 work on either.
applies_to is populated for those two categories only. A document has no such
axis, so it comes back empty there.
Render direction_labels, not words derived from kind
kind describes how the value is stored; direction_labels says what each
direction means for that field. They are not the same question, and one
field makes the difference concrete: tenure sorts on a start date, so a
date-shaped label offers "Newest first" for the request that actually returns
the longest-serving people. seniority is another: it sorts on the seniority
ladder rather than alphabetically, so its labels read "Most senior first" and
"Most junior first". Show the strings the route gives you.
group — identity, location, professional, firmographic or funding —
is the same grouping the column catalogue uses, so a sort picker and a column
picker file a field under the same heading. It is null on a document category,
which has no grouping.
An empty sorts is an answer
A category that cannot be ordered explicitly returns sorts: []. That does not
mean the results arrive unordered — the engine still orders them, it just does
not take instruction. Hide the control rather than showing an empty one.
Errors#
| Status | Code | Summary |
|---|---|---|
| 401 | auth.unauthorized | No usable credential (cause deliberately unspecified) |
| 403 | auth.browser_origin | A secret key was used from a browser |
| 403 | auth.insufficient_scope | The credential does not hold the required scope |
| 422 | pagination.cursor_invalid | The cursor could not be read (e.g. a filter changed mid-pagination) |
| 422 | request.invalid | A field failed validation (extra.errors has the detail) |
| 503 | credits.unavailable | The billing service could not confirm the charge; retry with the same Idempotency-Key |
| 503 | upstream.unavailable | The retrieval engine is unreachable (not charged) |
| 504 | upstream.timeout | The retrieval engine did not answer in time (not charged) |