Project ROUNDTABLE Docs
API reference

Search API

Government global search across four entity types

Search is an authenticated government convenience endpoint. It searches organizations, submissions, engagements, and Calls for Technology using a hybrid backend: PostgreSQL full-text search runs first — with boolean operators (AND implicit between words, OR, -word or !word negation), "quoted phrase" matching, and prefix* wildcards, plus an implicit prefix match on the last word of an operator-free query so type-ahead keeps matching a partially typed word — and when it finds nothing the search degrades to typo-tolerant pg_trgm trigram fuzzy matching (word_similarity() / %> over GIN trigram indexes), so misspelled queries like autonmy still match "autonomy" records. Queries using operator syntax never fall through to the fuzzy stage (it is operator-blind and would match excluded terms), so an operator query with no full-text matches returns an empty result. The fuzzy threshold defaults to 0.2 and is tunable via SEARCH_SIMILARITY_THRESHOLD; either stage is selectable on its own via SEARCH_BACKEND. All backends preserve this response contract.

GET /api/search

Auth: Any accepted government role. Success: 200.

Query fieldTypeRequired?Constraints
qstringYes1–255 characters.
limitintegerNoCoerced integer, 1–50; default backend limit 10.
typesCSV enum listNoorganization, submission, engagement, call; invalid values reject.
curl 'http://localhost:3000/api/search?q=Pelagic&limit=10&types=organization,submission' \
  -H 'cookie: next-auth.session-token=GOV_SESSION'

Example:

{"data":{"query":"Pelagic","total":2,"groups":[{"type":"organization","label":"Organizations","items":[{"type":"organization","id":"clx-pelagic","title":"Pelagic Dynamics","snippet":"Small business building low-cost autonomous undersea vehicles...","url":"/gov/organizations/clx-pelagic","meta":"COMPANY · ACTIVE · uuv,autonomy,acoustics"}]}]}}

The backend matches names/descriptions/tags for organizations, titles/summaries/tags for submissions, titles/summaries/location for engagements, and titles/descriptions/tags for calls. The full-text stage ranks by ts_rank relevance; the fuzzy stage scores the best-matching extent within a field, so a term buried inside a long description still matches. Each group is capped by the per-type limit, snippets are at most 160 characters, and results are grouped with a total.

Errors: 401 unauthenticated, 400 missing/too-long query or invalid type, 500 unexpected search/database failure. Search does not bypass destination-page authorization: opening a URL still applies the page's role and command scope.

Result item fields

FieldMeaning
typeOne of organization, submission, engagement, call.
idPrisma record ID.
titleDisplay title/name.
snippetUp to 160 characters, centered on a literal query occurrence when present (fuzzy-only matches fall back to the leading text).
urlIn-app destination.
metaOptional compact status, organization, command, date, or tags.

The result contract deliberately avoids returning full record bodies. The destination page performs the deeper read and its own authorization check.