Troubleshooting

Indexed by symptom, because several different causes produce the same one. Most of the entries below share a shape worth knowing up front: a query Indx refuses comes back looking like a query that simply found nothing.

The first check, for any "no results"

A refused search returns 200 with no records and a populated reason (Reason in C#). An honest no-match returns 200 with no records and reason: null. They differ in that one field and nowhere else.

ts
const result = await search(client, query);
if (result.reason || result.didTimeOut) {
  // Refused or timed out — a failure. Say so; never render it as "no results".
}

Do this once, in whatever wraps your search calls, and most of this page stops being able to bite you. Then read on for the cause.


Search returns nothing, for every query

A fieldBoosts key that is not a searchable field empties the whole search. Unknown fields and fields that were searchable until someone changed the configuration both do it. The response says so: reason carries "FieldBoosts contains unknown or non-searchable field" with the field's name, so the first check above catches it.

Check the boost keys against the configuration:

bash
curl -H "Authorization: Bearer $KEY" \
  ".../api/teams/$TEAM/datasets/$DATASET/fields/configuration"

Every key you send must appear there with "searchable": true. The tell is a surface split: the page that sends boosts returns nothing while a page that sends none works perfectly. If you keep weights and boosts in code, assert one against the other in a test — see the field configuration how-to.

The dataset is not Ready. GET status reports the state and, when it failed, errorMessage. A dataset that is loading, indexing or hibernated cannot serve.


An empty query returns nothing

Empty text needs enableFacets: true. Without it the query is refused rather than treated as match-everything, with reason set to Search called with empty string and EnableFacets=false. The dataset also needs at least one facetable field. See Empty search.


Inserts fail with "field(s) not in the field configuration"

The document carries a field the dataset does not know, a new field in your CMS, say. Insert and update refuse it with 400 invalidArgument naming the field, and store nothing: one such document fails its whole batch. (Until October 2026 the field was accepted, shown in hit payloads and never indexed, so "the field shows in results but searching on it finds nothing" is the same cause on an older server.)

Only Replace can add a field. Re-analyze and rebuild from a full export (POST …/replace, or Options → Replace data in the console), which reports the fields it added. Configuring roles and weights on existing fields does not need it.

The mirror image is worth knowing too: a field in your configuration that the engine has not discovered is rejected, taking the whole configuration call down with a 400 invalidArgument. One optional field that happens to be empty in every document is enough. Apply what the engine discovered and report the rest loudly, rather than sending a configuration the dataset cannot accept.


The result count is wrong, or suspiciously round

A count equal to coverageDepth is the cap, not your data. Coverage confirms only the first coverageDepth candidates (default 500), so a broad query reports exactly that many. Raise it to the document count; the cost is linear in depth, so on small and mid-sized datasets it is effectively free. See Deep coverage.

The list and the facet counts disagree. They ran at different depths, so they describe different universes. Use one depth per surface.


Results are ranked oddly after a change

fieldBoosts multiply the configured weight, they do not replace it. A field at weight: 3.0 with a boost of 3.0 counts 9×. Keep the ladder in the field configuration and use boosts only for what one surface wants differently.

Pattern matching always returns its nearest shapes, so a loose query can never answer "nothing" — and score does not separate a useful guess from noise. Search precisely first (coverageSetup.includePatternMatches: false); only if that finds nothing, search again with pattern matches on, and label those rows as guesses. See Exact hits and Pattern only.


A request that used to work returns 404 or 403

403 insufficientKeyScope — the key's access level is below what the endpoint needs. Search only keys search; reads like export and field configuration need Read only; writes need Full access.

404 — the team or dataset is outside the key's scope. This is deliberately the same answer as a team or dataset that does not exist, so names cannot be enumerated through a key. Two causes look identical here:

  • the key names datasets and this is not one of them;
  • the dataset was renamed. A key that lists datasets by name silently stops reaching a renamed one.
  • the team was renamed. The team name is part of every path (/api/teams/{team}/…), so calls to the old name find no team. The keys themselves keep working; update the path.
  • the key is a personal key and its owner left the team. A personal key acts as the person who made it. For anything deployed, use a team key (Manage team → API keys), which does not depend on one person's membership.

Keys cannot be edited after creation. To change a level or a dataset list, create a new key and revoke the old one.


Search works locally but not in production

The key belongs to the other server. Keys are signed per server, so a key minted on your local instance fails against the cloud with 401 and a signature error, and the reverse. Check the host the key was created on before anything else.

A VITE_-style build-time variable changed without a redeploy. Anything inlined at build time ships with the bundle; changing it in the hosting dashboard does nothing until the next deploy.


A term stays empty long after the index recovered

An empty answer was cached. A query that ran during a reload or against a not-ready dataset returns no records; cache that and the term stays empty for the life of the entry. Cache hits, not misses, and never cache a response carrying a reason or didTimeOut.


Nothing here matches

GET status is the first place to look: it carries the state, the document count, and errorMessage from the last refused call. Errors from the HTTP API are RFC 9457 problem documents with a machine-readable code — see the HTTP endpoints reference for the full table.