Response Signals
A search or count can succeed with 200 and still not mean what the numbers suggest. Nine optional fields say so. They appear on both POST /companies/search and POST /companies/counts — the same set on each, so a client that checks them only on search misses them on counts.
Blocking versus advisory#
This is the distinction to build on. It decides what your code should do and what you should show a user.
| Meaning | What to do | |
|---|---|---|
| Blocking | No usable results were returned. The count is not a count of anything. | Do not render a result set or a total. Tell the user to narrow, and say why. |
| Advisory | Results ARE returned, but they are incomplete or approximate. | Render them, and mark them as a floor rather than an answer. |
If you handle a blocking signal like an advisory, you render “these results are a floor” over an empty list — a hedge about data that was never returned. We shipped that bug twice, in the CLI and again in the web app, before splitting the two kinds explicitly.
Blocking signals#
Results were withheld. Narrow the filter and retry.
| Field | Set when | Fix |
|---|---|---|
job_posting_too_broad | A job-postings filter matched more companies than can be resolved at once. | Add a title, a location, or a minimum posting count. |
financing_too_broad | A financing filter matched too many companies — about 138,000 have some funding event. | Add a round type, a size, or a date range. |
news_too_broad | A news filter took the per-event path (a date bound with min_confidence, or both bounds) and matched too many. | Add an event type, tighten the dates, or raise the confidence. |
lookalike_seed_has_data | Set to false when the lookalikes seed has no similar-company data. About 90% of companies have none. | Nothing is wrong with the filter. Try another seed, or drop the lookalikes filter. |
It is the only one that signals a problem by being false rather than true. Check for === false, not for falsiness — it is absent entirely when no lookalikes filter is active.
Advisory signals#
Results came back, but they under-report. Show them with the caveat.
| Field | Type | Meaning |
|---|---|---|
news_events_undated | integer | Companies excluded by a news date bound because their events carry no date. Dates cover about 40% of news events, so the excluded set is often larger than the one returned. -1 means the count itself could not be retrieved. |
financing_events_undated | integer | Funding events excluded by a date filter for the same reason. Dates cover about 37% of financing events. |
engagement_set_capped | boolean | The workspace has saved or exported more companies than the engagement filter can hold, so Saved and Net-new figures are a lower bound rather than exact. |
keyword_phrases_collapsed | string[] | Keyword chips that matched far more broadly than written, because common English words are removed when descriptions are indexed. "Series A" matches on series alone. |
keyword_collapse_details | object[] | Per-chip detail for the above: phrase plus the matched_term it really matched on, or null when the chip matched nothing at all. Those are different failures and need different advice. |
Handling them#
const res = await api("POST", "/api/v1/companies/search", filters);
// BLOCKING — nothing usable came back. Do not show a count.
const blocked =
res.job_posting_too_broad ||
res.financing_too_broad ||
res.news_too_broad ||
res.lookalike_seed_has_data === false; // note: === false
if (blocked) {
return showNarrowPrompt(res);
}
// ADVISORY — render the results, but say they are a floor.
const caveats = [];
if (res.news_events_undated) caveats.push(`${res.news_events_undated} companies excluded (no event date)`);
if (res.financing_events_undated) caveats.push(`${res.financing_events_undated} funding events excluded (no date)`);
if (res.engagement_set_capped) caveats.push("Saved / Net-new are a lower bound");
if (res.keyword_phrases_collapsed?.length) caveats.push(`Keywords matched broadly: ${res.keyword_phrases_collapsed.join(", ")}`);
render(res.items, caveats);Every field is optional and absent when it does not apply, so a missing field and a “no problem” value mean the same thing — except lookalike_seed_has_data, noted above. The same handling works for counts.