Search
Site-wide full-text search across code, symbols, issues, merge requests, and documentation, returned as one grouped envelope with a degradation contract for clients that need to fall back when the search backend is unavailable.
Resource overview
Site search is a single endpoint:
GET /api/v1/search
It runs one cross-entity query and returns a grouped envelope with a section per entity type. The query is dispatched to the OpenSearch backend in a single cluster round-trip; when that backend is unavailable the handler degrades to an empty envelope rather than failing (see Degraded results).
Two narrower, deprecated endpoints remain for backward compatibility and have no
active client: GET /api/v1/search/code (code-only) and
GET /api/v1/search/suggest (type-ahead suggestions). New integrations should
call GET /api/v1/search. Per-repository symbol lookup
(GET /api/v1/repos/{owner}/{repo}/symbols) and issue search
(GET /api/v1/repos/{owner}/{repo}/issues/search) are scoped to a single
repository and are documented with their own resources.
Auth & scopes
See conventions.md for the accepted credential types. Site
search requires a valid bearer credential — an unauthenticated request is
answered 401. There is no dedicated search scope; instead, every result is
filtered to what the caller is allowed to read:
- An instance administrator searches across all repositories.
- A non-administrator sees hits only from repositories they can read
(
public,internal, andprivaterepositories where they are a member).
This visibility filter is applied to every section of every query, so search can never surface content from a repository the caller could not otherwise read.
Endpoints
| Method | Path | Summary |
|---|---|---|
GET |
/api/v1/search |
Cross-entity site search (grouped envelope) |
GET /api/v1/search
Search across the indexed entity types and return a grouped result envelope. Requires authentication; results are visibility-filtered to the caller.
Query parameters
| Name | Type | Default | Description |
|---|---|---|---|
q |
string | — | Query string. Required; an empty value returns 400. |
types |
string | all | Comma-separated entity types to query. See Search types. Omitted means the default set. Unrecognized types are ignored. |
limit |
integer | 20 |
Maximum hits per section. Clamped to [1, 100]; a value of 0 or a non-numeric value falls back to 20. |
sort |
string | relevance |
relevance (default) or recent. An unrecognized value is silently treated as relevance — this endpoint does not return 400 for a malformed sort. |
Search types
The types parameter selects which sections are populated. The recognized
values are:
| Type | Searches |
|---|---|
code |
Source files across readable repositories. |
symbols |
Code symbols (definitions) across readable repositories. |
issues |
Issues. |
mrs |
Merge requests. |
docs |
Markdown in the configured documentation repositories. |
When types is omitted the default set is code, symbols, issues, and
mrs. The docs type is opt-in: it is not part of the default set and is
populated only when the caller asks for it explicitly (for example
types=docs). The docs section is a view over the indexed Markdown of the
configured documentation repositories; a deployment with no documentation
repositories configured returns an empty docs section.
sort=recent orders the issues and mrs sections by their most recent update.
The code, symbols, and docs sections have no date anchor and stay
relevance-ranked even when sort=recent is requested.
Response
The envelope always carries all five sections. Each section has a total and a
hits array; hits is always an array (never null), even for an unselected
or empty section. Each hit carries an id, a relevance score, and a source
object whose fields are specific to the entity type.
{
"code": {
"total": 1,
"hits": [
{
"id": "alice/widgets:src/main.go",
"score": 8.42,
"source": {
"repo_owner": "alice",
"repo_name": "widgets",
"path": "src/main.go",
"lang": "go"
}
}
]
},
"symbols": { "total": 0, "hits": [] },
"issues": { "total": 0, "hits": [] },
"mrs": { "total": 0, "hits": [] },
"docs": { "total": 0, "hits": [] }
}
A query that matches nothing — or one served while the backend is degraded —
returns the same envelope with every section at { "total": 0, "hits": [] }.
Degraded results
Site search depends on the OpenSearch backend. When that backend cannot serve
the query — it is not wired on the deployment, the query errored, or the
fail-closed safeguard tripped — the handler returns 200 OK with the empty
envelope above and sets a response header:
X-Search-Fallback: true
The header signals that the empty result is a degraded response, not a genuine
"no results". A healthy backend never sets the header, even when the query
legitimately matched zero documents. A client that maintains its own fallback
index reads this header to decide whether to fall back. The signal is
per-response, not per-section: a degraded types=docs request carries it
exactly as a degraded types=code request does.
Status codes
| Status | When |
|---|---|
200 OK |
Results returned (including a degraded empty envelope — see above). |
400 Bad Request |
q is missing or empty. |
401 Unauthorized |
No valid credential. |
429 Too Many Requests |
The api.expensive budget was exceeded — see Rate limits. |
500 Internal Server Error |
The visibility filter could not be resolved. This is distinct from an OpenSearch outage, which degrades to 200. |
Example
curl -H "Authorization: Bearer <token>" \
"https://api.gitvetrix.com/api/v1/search?q=widget&types=code,issues&limit=10"
Include the documentation section explicitly:
curl -H "Authorization: Bearer <token>" \
"https://api.gitvetrix.com/api/v1/search?q=deployment&types=docs"
Errors
These endpoints use the shared error envelope and status-code conventions in
errors.md. Note the deliberate split above: a missing query is a
client error (400) and an unresolvable visibility filter is a server error
(500), but an OpenSearch outage is not an error — it degrades to a 200
empty envelope with X-Search-Fallback: true.
Rate limits
Site search is metered under api.expensive rather than api.read, because a
single request fans out across multiple indices. The deprecated
GET /api/v1/search/code and GET /api/v1/search/suggest endpoints are metered
the same way. See rate-limits.md for the per-group budgets
and the 429 response.