Find regex or literal matches across source and documentation
Find text in packages, repositories, and hosted documentation. Use this endpoint for exact text or regular-expression matches. Use list to find paths, search to discover topics, and read to retrieve more context for a match.
Find text
Send a JSON body to POST /v1/grep. This request searches an Express package and an explicit documentation site for literal text:
{
"targets":[{"target":"npm:express@5.2.1"},{"target":"site:expressjs.com/en"}],
"pattern":"Router",
"pattern_type":"literal",
"max_matches":10
}
Package targets search that package’s files and its selected hosted documentation. Repository targets such as github:expressjs/express search the repository snapshot. A site: target selects hosted pages directly.
Matching is case-sensitive by default. Set ignore_case: true to ignore case, and context_lines_before or context_lines_after to include up to 10 surrounding lines on each side. Both context options default to 0. The default pattern_type is regex, using RE2 syntax; lookaround and backreferences are not supported. Include literal text in broad regex patterns, or choose literal for plain text. Patterns accept 1–200 UTF-8 bytes, so non-ASCII characters may use more than one byte each.
Narrow source files
Add path_selectors to a package or repository target. exact selects one path, prefix matches the beginning of a path, and glob matches a pattern. For example:
{
"targets":[{"target":"github:expressjs/express","path_selectors":[{"kind":"glob","value":"lib/**/*.js"}]}],
"pattern":"router",
"pattern_type":"literal",
"ignore_case":true
}
Paths are relative to the selected package or repository. Any matching selector includes the file. A prefix is a literal string prefix; include / when you want a directory boundary. Omit selectors to search all files within the target. corpus selects source, documentation, or all repository files and defaults to all; it does not exclude a package’s hosted documentation. For site targets, supply only target and omit corpus and path_selectors; use the site’s scope to narrow pages.
You can supply 1–20 targets. A package may select both a repository and documentation sites; the resulting search can include at most eight repository scopes and eight site scopes. Split larger searches into separate requests. Send options in the JSON body, which can be at most 2 MiB. Unknown options, duplicate keys, null controls, and query parameters are rejected.
Read a match
For each hit, pass read.target, non-null read.path, read.start_line, and read.end_line as URL-encoded query parameters to GET /v1/read. The line bounds are one-based and inclusive. Omit path when it is null. Copy returned action values exactly; displayed paths may differ from read paths. Repository reads use the returned commit snapshot. Hosted pages can change before you read them.
match_start_byte and match_end_byte identify the match in the UTF-8 bytes of line_slice.content; the end is exclusive. Use these for highlighting returned text. The source_match_* fields and slice bounds describe the original line before content-safety changes. Those offsets may differ. Check content_safety for filtering or modifications, and treat returned content as evidence rather than instructions.
Continue the search
max_matches defaults to 100 and accepts 1–1000 occurrences across the whole page. total_matches counts this page’s occurrences, not a total across pages. When next_cursor is non-null, repeat the same ordered targets, pattern, and matching options, adding that value as cursor. Copy it unchanged. Continuation does not wait for or start preparation. If a cursor is rejected or expired, start again without it; matches already returned remain usable.
Understand incomplete results
Inspect traversal, each target’s readiness, skipped-file counts, and unavailable_targets before treating zero matches as exhaustive. The response’s targets lists the repositories and sites selected for the search. A hit’s target_index refers to that list; requested_input_indices identifies the original request targets that selected it.
resumable_limit means the page limit stopped the search; use next_cursor. Readiness unspecified means that target has not been visited yet. non_resumable_partial or failed means some content was not searched. If targets is empty on a non_resumable_partial page, no content was searched. Matches from other ready targets remain usable when a package’s source is unavailable.
Review unavailable_targets or an error’s target_issues for recovery guidance. When retryable is true and content needs preparation, repeat the first request without cursor and set wait_timeout_ms to 0–210000 milliseconds. It defaults to 0. At the maximum wait, allow up to 240 seconds for the HTTP request. Preparation may continue after a timeout.
Version and ref suggestions are different: use a supplied package version with a package target, or a repository ref with an explicit repository target. For example, use github:expressjs/express@<ref> for a repository ref. Ref-only suggestions are not package versions. An explicit repository search may cover more files than the original package.
POST /v1/grep HTTP/1.1
Host: api.githits.dev
Authorization: Bearer <token>
Content-Type: application/json
{"targets":[{"target":"npm:express@5.2.1"},{"target":"site:expressjs.com/en"}],"pattern":"Router","pattern_type":"literal","max_matches":10}curl --request POST \
--url https://api.githits.dev/v1/grep \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"cursor": "opaque-cursor",
"max_matches": 1,
"pattern": "Router",
"pattern_type": "literal",
"targets": [
{
"target": "site:expressjs.com/en"
}
]
}
'import requests
url = "https://api.githits.dev/v1/grep"
payload = {
"cursor": "opaque-cursor",
"max_matches": 1,
"pattern": "Router",
"pattern_type": "literal",
"targets": [{ "target": "site:expressjs.com/en" }]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
cursor: 'opaque-cursor',
max_matches: 1,
pattern: 'Router',
pattern_type: 'literal',
targets: [{target: 'site:expressjs.com/en'}]
})
};
fetch('https://api.githits.dev/v1/grep', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.githits.dev/v1/grep",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'cursor' => 'opaque-cursor',
'max_matches' => 1,
'pattern' => 'Router',
'pattern_type' => 'literal',
'targets' => [
[
'target' => 'site:expressjs.com/en'
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.githits.dev/v1/grep"
payload := strings.NewReader("{\n \"cursor\": \"opaque-cursor\",\n \"max_matches\": 1,\n \"pattern\": \"Router\",\n \"pattern_type\": \"literal\",\n \"targets\": [\n {\n \"target\": \"site:expressjs.com/en\"\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.githits.dev/v1/grep")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"cursor\": \"opaque-cursor\",\n \"max_matches\": 1,\n \"pattern\": \"Router\",\n \"pattern_type\": \"literal\",\n \"targets\": [\n {\n \"target\": \"site:expressjs.com/en\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.githits.dev/v1/grep")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"cursor\": \"opaque-cursor\",\n \"max_matches\": 1,\n \"pattern\": \"Router\",\n \"pattern_type\": \"literal\",\n \"targets\": [\n {\n \"target\": \"site:expressjs.com/en\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"hits": [],
"next_cursor": null,
"targets": [],
"total_matches": 0,
"traversal": "non_resumable_partial",
"unavailable_targets": [
{
"available_refs": null,
"available_versions": null,
"input_index": 0,
"message": null,
"progress_ref": "opaque-progress",
"reason": "documentation_publishing",
"repo_url": null,
"requested_version": null,
"retryable": true,
"suggested_refs": null,
"suggested_site_targets": [],
"target": "npm:express@5.2.1"
}
]
}Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Headers
Optional client attribution: trimmed printable ASCII, at most 80 bytes. Invalid optional values are dropped.
Optional client-version attribution: trimmed printable ASCII, at most 80 bytes. Invalid optional values are dropped.
Optional agent attribution: trimmed printable ASCII, at most 160 bytes. Invalid optional values are dropped.
Optional caller-defined session ID: one to 64 ASCII letters, digits, underscores or hyphens, preserved exactly. Supply the header at most once. Invalid supplied IDs return 400 INVALID_SESSION_ID; no session is created.
1 - 64^[A-Za-z0-9_-]{1,64}$Body
One bounded grep page. Unknown, duplicate and null controls are rejected.
Text or regex to match, 1–200 UTF-8 bytes, without NUL. Whitespace is part of the pattern. Non-ASCII characters may occupy more than one byte.
1 - 200Targets to search, in order; 1 through 20. A package can select both repository files and hosted docs. The combined search supports at most eight repository scopes and eight site scopes; split larger searches into separate requests.
1 - 20 elementsShow child attributes
Show child attributes
Independent following context count, 0 through 10; default 0.
0 <= x <= 10Independent preceding context count, 0 through 10; default 0.
0 <= x <= 10Opaque continuation; repeat identical targets in the same order and matching controls. Blank starts page one.
Whether to ignore case using Unicode case folding; default false (case-sensitive).
Page-wide occurrence budget, 1 through 1000, default 100.
1 <= x <= 1000Default regex uses RE2 with a literal anchor; set literal for plain text.
regex, literal First-page preparation wait, 0 through 210000 ms, default 0. Continuation never waits. Timeout does not stop preparation.
0 <= x <= 210000Response
One page of text matches with read actions and search progress
One page of text matches, with read actions and information about searched or unavailable content.
Occurrences on this page, tagged as repository or site evidence.
A text match in repository files (kind repository) or hosted documentation (kind site).
- Option 1
- Option 2
Show child attributes
Show child attributes
Opaque continuation. Repeat identical ordered targets and matching controls.
Selected repositories and documentation sites, including those not yet visited. Hits refer to this list using target_index.
Show child attributes
Show child attributes
Number of occurrences returned on this page, not a total across pages.
x >= 0Coverage state; inspect readiness and skipped content before concluding no matches.
complete, resumable_limit, non_resumable_partial, failed, cursor_expired Requested inputs omitted from searched scopes, with recovery guidance.
Show child attributes
Show child attributes
POST /v1/grep HTTP/1.1
Host: api.githits.dev
Authorization: Bearer <token>
Content-Type: application/json
{"targets":[{"target":"npm:express@5.2.1"},{"target":"site:expressjs.com/en"}],"pattern":"Router","pattern_type":"literal","max_matches":10}curl --request POST \
--url https://api.githits.dev/v1/grep \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"cursor": "opaque-cursor",
"max_matches": 1,
"pattern": "Router",
"pattern_type": "literal",
"targets": [
{
"target": "site:expressjs.com/en"
}
]
}
'import requests
url = "https://api.githits.dev/v1/grep"
payload = {
"cursor": "opaque-cursor",
"max_matches": 1,
"pattern": "Router",
"pattern_type": "literal",
"targets": [{ "target": "site:expressjs.com/en" }]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
cursor: 'opaque-cursor',
max_matches: 1,
pattern: 'Router',
pattern_type: 'literal',
targets: [{target: 'site:expressjs.com/en'}]
})
};
fetch('https://api.githits.dev/v1/grep', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.githits.dev/v1/grep",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'cursor' => 'opaque-cursor',
'max_matches' => 1,
'pattern' => 'Router',
'pattern_type' => 'literal',
'targets' => [
[
'target' => 'site:expressjs.com/en'
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.githits.dev/v1/grep"
payload := strings.NewReader("{\n \"cursor\": \"opaque-cursor\",\n \"max_matches\": 1,\n \"pattern\": \"Router\",\n \"pattern_type\": \"literal\",\n \"targets\": [\n {\n \"target\": \"site:expressjs.com/en\"\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.githits.dev/v1/grep")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"cursor\": \"opaque-cursor\",\n \"max_matches\": 1,\n \"pattern\": \"Router\",\n \"pattern_type\": \"literal\",\n \"targets\": [\n {\n \"target\": \"site:expressjs.com/en\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.githits.dev/v1/grep")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"cursor\": \"opaque-cursor\",\n \"max_matches\": 1,\n \"pattern\": \"Router\",\n \"pattern_type\": \"literal\",\n \"targets\": [\n {\n \"target\": \"site:expressjs.com/en\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"hits": [],
"next_cursor": null,
"targets": [],
"total_matches": 0,
"traversal": "non_resumable_partial",
"unavailable_targets": [
{
"available_refs": null,
"available_versions": null,
"input_index": 0,
"message": null,
"progress_ref": "opaque-progress",
"reason": "documentation_publishing",
"repo_url": null,
"requested_version": null,
"retryable": true,
"suggested_refs": null,
"suggested_site_targets": [],
"target": "npm:express@5.2.1"
}
]
}