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.

GET/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:

  • sort on POST /v1/search says 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.

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
}
  • direction is asc or desc and defaults to desc. Every field accepts both.
  • Omit sort entirely to get the engine's own ordering — relevance when there is a query to 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.
  • sort needs an explicit category. Omitting category asks for auto-classification, and the sortable fields differ per category, so there would be nothing to validate the clause against. The pair is a 422.
  • A repeated field is a 422 — 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

categoryEntityCategoryrequired

Which 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 string
metaResponseMeta

Present on every 2xx, including the ones that are not paginated.

as_ofstring
credential"api_key" | "session"default: api_key

Which 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 | null

What 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_idstring
sortsarray of SortFieldInfo

Empty 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 string

company, 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_directionstring

What 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 | null

What the field means, in words. Null where none is recorded.

direction_labelsDirection Labels

What 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 string

Always both, today.

fieldstring

The value to send as sort[].field.

groupstring | null

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

kindstring

number, date or text. Enough to choose a control; not a parser — the values themselves never come back through this route.

labelstring

Display name. Matches the column's own label.

totalinteger

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

StatusCodeSummary
401auth.unauthorizedNo usable credential (cause deliberately unspecified)
403auth.browser_originA secret key was used from a browser
403auth.insufficient_scopeThe credential does not hold the required scope
422pagination.cursor_invalidThe cursor could not be read (e.g. a filter changed mid-pagination)
422request.invalidA field failed validation (extra.errors has the detail)
503credits.unavailableThe billing service could not confirm the charge; retry with the same Idempotency-Key
503upstream.unavailableThe retrieval engine is unreachable (not charged)
504upstream.timeoutThe retrieval engine did not answer in time (not charged)