Search
Per-type search over shithub's Postgres FTS corpus. Every result set is filtered by the caller's visibility — anonymous callers see only public rows.
Response shape
Every endpoint returns the canonical gh-compatible envelope:
{
"total_count": 142,
"incomplete_results": false,
"items": [
{ /* type-dependent record */ }
]
}
incomplete_results is always false in v1 (the FTS pipeline
runs to completion before responding). The field is preserved on
the wire so future ranker timeouts can flip it.
total_count is the unbounded result count.
Query syntax
The q= parameter is parsed by internal/search/query_parse —
free-text terms plus the following operators:
repo:owner/name— restrict to one repois:open/is:closed(orstate:open/state:closed)author:username"quoted phrase"— phrase search (one phrase per query)
Unknown prefixes (e.g. language:Go) fall through as free text
so future operator additions don't break older queries.
Search repositories
GET /api/v1/search/repositories?q=...&page=N&per_page=M
Scope: repo:read for authenticated callers; anonymous callers
fall through to the public-only filter.
{
"total_count": 1,
"incomplete_results": false,
"items": [
{
"id": 12,
"name": "demo",
"full_name": "alice/demo",
"owner_login": "alice",
"description": "demo repo",
"visibility": "public",
"private": false,
"star_count": 0,
"updated_at": "2026-05-12T05:00:00Z",
"score": 0.42
}
]
}
Search issues
GET /api/v1/search/issues?q=...&type=issue|pr
Scope: repo:read. type=issue or type=pr narrows to one
kind; omit to return both (issues share their table with PRs).
{
"id": 17,
"number": 1,
"repo_id": 12,
"repo": "alice/demo",
"title": "needle in haystack",
"state": "open",
"kind": "issue",
"author_name": "alice",
"updated_at": "2026-05-12T05:00:00Z",
"score": 0.81
}
Search code
GET /api/v1/search/code?q=...
Scope: repo:read. Matches against the path-and-content index
populated by the push pipeline. preview_line carries a single
line of context for content hits; path-only hits omit it.
{
"repo_id": 12,
"repo": "alice/demo",
"ref": "trunk",
"path": "internal/foo/foo.go",
"preview_line": "func Foo() error { return errors.New(\"foo\") }",
"score": 0.55
}
Pagination
?per_page= (≤100, default 30) and ?page= are supported, with
the standard Link: header (see overview). The
total_count field is independent of the page slice — it counts
all matching rows regardless of pagination.
Not yet shipped
GET /api/v1/search/commits— commit message search; requires a per-repo commit FTS index that doesn't exist yet.GET /api/v1/search/users— backing query exists; REST surface pending; will land alongside §7 orgs/users follow-up.sort=/order=— every endpoint currently sorts by FTS rank; exposing alternative sorts (created, updated, stars) is queued.
View source
| 1 | # Search |
| 2 | |
| 3 | Per-type search over shithub's Postgres FTS corpus. Every result |
| 4 | set is filtered by the caller's visibility — anonymous callers |
| 5 | see only public rows. |
| 6 | |
| 7 | ## Response shape |
| 8 | |
| 9 | Every endpoint returns the canonical gh-compatible envelope: |
| 10 | |
| 11 | ```json |
| 12 | { |
| 13 | "total_count": 142, |
| 14 | "incomplete_results": false, |
| 15 | "items": [ |
| 16 | { /* type-dependent record */ } |
| 17 | ] |
| 18 | } |
| 19 | ``` |
| 20 | |
| 21 | `incomplete_results` is always `false` in v1 (the FTS pipeline |
| 22 | runs to completion before responding). The field is preserved on |
| 23 | the wire so future ranker timeouts can flip it. |
| 24 | |
| 25 | `total_count` is the unbounded result count. |
| 26 | |
| 27 | ## Query syntax |
| 28 | |
| 29 | The `q=` parameter is parsed by `internal/search/query_parse` — |
| 30 | free-text terms plus the following operators: |
| 31 | |
| 32 | - `repo:owner/name` — restrict to one repo |
| 33 | - `is:open` / `is:closed` (or `state:open` / `state:closed`) |
| 34 | - `author:username` |
| 35 | - `"quoted phrase"` — phrase search (one phrase per query) |
| 36 | |
| 37 | Unknown prefixes (e.g. `language:Go`) fall through as free text |
| 38 | so future operator additions don't break older queries. |
| 39 | |
| 40 | ## Search repositories |
| 41 | |
| 42 | ``` |
| 43 | GET /api/v1/search/repositories?q=...&page=N&per_page=M |
| 44 | ``` |
| 45 | |
| 46 | Scope: `repo:read` for authenticated callers; anonymous callers |
| 47 | fall through to the public-only filter. |
| 48 | |
| 49 | ```json |
| 50 | { |
| 51 | "total_count": 1, |
| 52 | "incomplete_results": false, |
| 53 | "items": [ |
| 54 | { |
| 55 | "id": 12, |
| 56 | "name": "demo", |
| 57 | "full_name": "alice/demo", |
| 58 | "owner_login": "alice", |
| 59 | "description": "demo repo", |
| 60 | "visibility": "public", |
| 61 | "private": false, |
| 62 | "star_count": 0, |
| 63 | "updated_at": "2026-05-12T05:00:00Z", |
| 64 | "score": 0.42 |
| 65 | } |
| 66 | ] |
| 67 | } |
| 68 | ``` |
| 69 | |
| 70 | ## Search issues |
| 71 | |
| 72 | ``` |
| 73 | GET /api/v1/search/issues?q=...&type=issue|pr |
| 74 | ``` |
| 75 | |
| 76 | Scope: `repo:read`. `type=issue` or `type=pr` narrows to one |
| 77 | kind; omit to return both (issues share their table with PRs). |
| 78 | |
| 79 | ```json |
| 80 | { |
| 81 | "id": 17, |
| 82 | "number": 1, |
| 83 | "repo_id": 12, |
| 84 | "repo": "alice/demo", |
| 85 | "title": "needle in haystack", |
| 86 | "state": "open", |
| 87 | "kind": "issue", |
| 88 | "author_name": "alice", |
| 89 | "updated_at": "2026-05-12T05:00:00Z", |
| 90 | "score": 0.81 |
| 91 | } |
| 92 | ``` |
| 93 | |
| 94 | ## Search code |
| 95 | |
| 96 | ``` |
| 97 | GET /api/v1/search/code?q=... |
| 98 | ``` |
| 99 | |
| 100 | Scope: `repo:read`. Matches against the path-and-content index |
| 101 | populated by the push pipeline. `preview_line` carries a single |
| 102 | line of context for content hits; path-only hits omit it. |
| 103 | |
| 104 | ```json |
| 105 | { |
| 106 | "repo_id": 12, |
| 107 | "repo": "alice/demo", |
| 108 | "ref": "trunk", |
| 109 | "path": "internal/foo/foo.go", |
| 110 | "preview_line": "func Foo() error { return errors.New(\"foo\") }", |
| 111 | "score": 0.55 |
| 112 | } |
| 113 | ``` |
| 114 | |
| 115 | ## Pagination |
| 116 | |
| 117 | `?per_page=` (≤100, default 30) and `?page=` are supported, with |
| 118 | the standard `Link:` header (see [overview](overview.md)). The |
| 119 | `total_count` field is independent of the page slice — it counts |
| 120 | all matching rows regardless of pagination. |
| 121 | |
| 122 | ## Not yet shipped |
| 123 | |
| 124 | - `GET /api/v1/search/commits` — commit message search; requires a |
| 125 | per-repo commit FTS index that doesn't exist yet. |
| 126 | - `GET /api/v1/search/users` — backing query exists; REST surface |
| 127 | pending; will land alongside §7 orgs/users follow-up. |
| 128 | - `sort=` / `order=` — every endpoint currently sorts by FTS rank; |
| 129 | exposing alternative sorts (created, updated, stars) is queued. |