Getting started
The response envelope
The shape every list endpoint returns, field by field — including the fields most clients ignore and shouldn't.
The envelope is the fixed set of fields wrapped around every answer the API gives you: the results themselves, plus the fields that tell you how to read them. How many matches exist, whether there are more pages, how your query was interpreted, and what (if anything) limited the answer.
Every list-returning endpoint returns this same shape, with the route's own rows in results[] and its own extra side-fields beside them:
/v1/searchand/v1/similar- the four
/v1/relationships*routes /v1/org-chart*- all nine
/v1/entities/*routes - an agent run's results,
GET /v1/agents/runs/{run_id}/results GET /v1/usage/history
Learn it once and every response on the platform reads the same.
{
"results": [ { "id": "solarisgroup.com", "category": "company", "score": 0.93, "account_name": "Solaris SE", "...": "..." } ],
"total": 10000,
"total_is_estimate": true,
"has_more": true,
"next_cursor": "eyJmIjoiMDNjMjUzNzk3MjM3MTI3ZSIsIm8iOjEs...",
"resolved_search": { "parser_version": "none", "mode": "fast", "category": "company", "query": "fintech companies in berlin", "filters": {}, "filters_source": "none", "semantic_query": "fintech companies in berlin", "semantic_scope": null, "sort": [], "conflicts": [], "overrides": [] },
"suggestion": null,
"coverage": null,
"attribution": [ { "source_corpus": "gtm_index", "domain": null, "as_of": "2026-08-12T19:52:31Z", "url": null, "quote": null } ],
"degradation": [],
"columns": null,
"meta": { "request_id": "fc8735204043c339af9e9cbffaff2b21", "environment": "live", "as_of": "2026-08-12T19:52:31Z", "credential": "api_key", "credits_charged": 0.5 }
}Why fields are never omitted#
Success responses always serialize every envelope field, even where the content lands later. This is the additive-only contract: adding a field to a response clients already parse is free, but a field that appears later is a breaking change for every client that must branch on it.
Field by field#
results[]
The hits. On search and similar, record fields are flattened onto the row beside three guaranteed keys:
| Key | Meaning |
|---|---|
id | Stable row identity. For a company with a domain, the domain itself ("solarisgroup.com"); everything else gets a stable 16-hex one-way hash. Safe as a de-dupe or render key; the same real-world row always gets the same id. Never the retrieval engine's internal record id |
category | Always the category you searched |
score | Relevance/similarity score |
Everything else varies by category: a company row carries account_name, domain, industry, employee_count, and ~25 more; a patent row carries title, abstract, applicants, …
total and total_is_estimate
total is the match count. total_is_estimate says whether it is exact. It is always serialized, never defaulted away, because a field clients can forget exists is a field that renders an estimate as exact.
has_more and next_cursor
next_cursor is opaque and signed; pass it back as cursor to get the next page.
- Invariant on every response:
has_more == (next_cursor != null). - On a cursor route there is no page or offset parameter: not accepted, not deprecated, never present. A few routes that aren't cursor-paged page differently: the filter-values routes take
page,GET /v1/feedbacktakesoffset, andGET /v1/agents/runstakeslimitonly.
Full semantics (fingerprint freezing, 24h expiry, 10,000-result depth): Pagination.
resolved_search
What the API decided your request meant. Present on search and similar, null on entities. It lets you re-run exactly the search you got, and explains result changes after a parser retune.
| Field | Meaning |
|---|---|
parser_version | The extraction agent's version when it ran; "none" when no LLM was in the path (all fast and similar calls, and cursor pages) |
mode, category, query | The request as interpreted |
filters | The effective filters actually searched |
filters_source | caller | parser | caller+parser (yours win on conflict) | none |
semantic_query | The text used for semantic retrieval, when any |
semantic_scope | What semantic_query describes. company on a person search means the description was matched against each person's employer (people have no embeddings of their own). null otherwise, including when there is no description. The schema also allows person, which is not sent today |
sort | The ordering actually applied: the clauses you sent, or one inferred from your question when you sent none. [] means the engine chose the order itself. See Sort fields |
conflicts | Contradictions between your filters and parsed ones |
overrides | Where your explicit filter overrode an inferred one |
suggestion
A better way to have asked this question, or null, which is the common case.
{ "kind": "agentic", "category": null, "message": "This needs several steps joined — run it as an agent run." }The split is search vs agentic, because that is the bigger question and the one you answer first:
kind | Means | category | Act on it with |
|---|---|---|---|
search | A plain POST /v1/search still answers this, just not in the category it was asked in | The one that fits; drops straight into the next request | POST /v1/search |
agentic | No single search answers this, whatever the category: it needs several steps, or several corpora joined | null, because the category is not the problem | POST /v1/agents/runs |
message is one ready-to-show sentence.
Why there is no mode field
The customer-facing agentic surface is its own endpoint, and a suggestion naming a parameter would point at the knob rather than at the thing that answers the question.
coverage
How much of the addressable set this answer represents:
universe: matching records, counted rather than sampledenumerated: how many this answer carriesenumeration_recall,universe_exhausted,truncated_byandconstraints_unmet[]
Two fields are worth reading carefully:
enumeration_recallisnullwhenever the universe was not exact, because a ratio over an estimate is not a recall figure. It measures coverage of the resolved query, not of your intent: a wrong predicate can be exhaustively enumerated.constraints_unmet[]names, in your own words, the requirements the search could not express. Non-empty means part of the question was not answered.
columns
The answer table's columns, in order, when an agent run's results
carry extra columns beyond the entity's own fields because the run was asked
for enrichments: a job-opening link, a value joined from another dataset. Each entry is {name, description, source}, and each column's value
sits on every row in results keyed by the column name.
null means a plain entity table, which is what an ordinary search returns.
attribution[]
Where the response's content came from: response-level in v1, a source rather than a citation. Each entry is {source_corpus, domain, as_of, url, quote}, and as_of is per-corpus freshness.
degradation[]
What the API could not do for this request, and what it cost the answer. An empty list means "we checked and nothing degraded", never "unknown". Each entry:
{ "reason": "vector_coverage_incomplete", "detail": "…", "impact": "recall", "source_corpus": "gtm_index" }impact is recall | precision | freshness | fields. The reason values and how to handle each are listed in the search guide.
meta
Present on every 2xx, including unpaginated ones (/v1/credits, /v1/keys/usage carry meta alone, without the list fields).
| Field | Meaning |
|---|---|
request_id | Trace id — also in the X-Request-Id header. Log it |
environment | live or test, echoed on every response so a test-key result can never be mistaken for a live one |
as_of | When the response was produced |
credits_charged | 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. null on a free endpoint. See Credits & usage |
credential | api_key or session — which credential served the request. session means the dashboard playground ran it: billed to the user, interactively throttled, no integration compatibility guarantee |
EntityHit — the entities result shape#
On /v1/entities/* routes, each element of results[] is an EntityHit rather than a flattened record:
{
"id": "adyen.com",
"category": "company",
"found": true,
"matches": [{ "account_name": "Adyen", "industry": "Finance", "...": "..." }]
}id: the input id, echoed back (order and duplicates preserved).found:falsefor a miss. Misses appear inresults[]rather than being dropped: you are billed against what you asked for, so you must see exactly what you got.matches: always a list, and[]on a miss. The record is typed for companies and persons, and the vertical's own vocabulary for document categories.
/v1/entities/* envelopes are never paginated: total is the exact hit count, has_more is false, next_cursor and resolved_search are null.