API and MCP tool reference

searchcode enterprise has one HTTP API, and everything uses it: the web UI, your scripts, and the MCP tools your agents call. This page is generated from the same definitions as the router, so it describes what the server actually accepts.

Every instance also serves this reference itself, with no credential needed: the rendered page at /api/v1/docs and the OpenAPI document at /api/v1/openapi.json. Neither is built from your estate, so publishing them discloses nothing about your repositories.

The basics

POST /api/v1/search

Search code across the estate

MCP tool: search. Authentication: API key or OAuth access token

One content search across every repository the caller may see, merged into one ranked set with one total.

CHOOSE A GRANULARITY FIRST — each rung is roughly an order of magnitude cheaper than the next, so this one parameter decides whether an answer costs 200 tokens or 20,000: repos — repository names and match counts, no paths, no content. Roughly a hundredth the cost of snippets. Use it for "does anyone use this at all", which is the question this product exists to answer. files — paths and per-file counts, no content. Roughly a tenth the cost of snippets. Use it for "which files". snippets — ranked snippets with a little context. THE DEFAULT. Use it for "how does this work". lines — every matching line, with context lines around each. The most expensive rung by an order of magnitude. Use it for "show me every use", and set line_limit.

Two mistakes to avoid, both common: OVER-SEARCHING. Once you have identified a file, READ IT with read_file rather than searching it again. Repeated searches against one file cost more than the file. OVER-NARROWING. Terms are ANDed, so four terms usually return nothing. Start with one or two terms and narrow with the filters below — never with more terms. An empty result means fewer terms, not more.

A ZERO IS NEVER BARE. When a query of plain terms matches nothing, notices says why: which of your words matched no file at all, and how many files each of the others is in. Read it before concluding anything. An empty result whose notice says some terms matched nothing is NOT evidence that the code is absent — it is evidence that one of your words was, and the same question asked without that word frequently answers it.

Every result carries the repository it came from, at every granularity. Across an estate, attributing code to the wrong service is the expensive mistake.

Snippets come back as hunks: contiguous runs of lines, each with the line number of its first line, so no line is ever returned twice however close two matches are. A gap between two hunks means lines were SKIPPED — the code is not contiguous, and reading it as though it were is a wrong conclusion about the file. match_lines says which lines matched and match_ranges says where within them, as byte offsets the engine reported — so you never have to guess which part of a line the query hit, and a line absent from match_ranges still matched.

Terms are ANDed by default. OR, NOT and (grouping) are supported, "quoted phrases" match literally, /regex/ matches as a regular expression and term~1 matches fuzzily. In-query filters are file:, path:, lang:, ext:, repo: and tech:, each accepting = and != and comma-separated multi-values.

PRECEDENCE TRAP: an in-query filter binds to the ADJACENT TERM ONLY. a OR b path:src parses as a OR (b AND path:src), so matches for a leak in from anywhere in the estate. Use the top-level path, file, language and include_ext parameters when you want the filter ANDed against the WHOLE query.

Ranking is tuned server-side by the operator and is not a parameter. There is no gravity, profile or ranker option, and passing one is refused rather than ignored — an agent that believes it is tuning something has been misled.

Request body:

Name In Type Required Default Maximum Description
query body string no The search query. Optional only in the sense that a filter may stand in for it: give a query, or scope the request with repo, language, path or tech and get a listing of what is in scope. Terms are ANDed by default. OR, NOT and (grouping) are supported, "quoted phrases" match literally, /regex/ matches as a regular expression and term~1 matches fuzzily. In-query filters are file:, path:, lang:, ext:, repo: and tech:, each accepting = and != and comma-separated multi-values. PRECEDENCE TRAP: an in-query filter binds to the ADJACENT TERM ONLY. a OR b path:src parses as a OR (b AND path:src), so matches for a leak in from anywhere in the estate. Use the top-level path, file, language and include_ext parameters when you want the filter ANDed against the WHOLE query.
granularity body string no snippets How much of the answer to return, cheapest first: repos (names and counts only), files (paths and counts), snippets (the default), lines (every matching line). Each rung costs roughly ten times the one before it, so start coarse and go finer only once you know what you are looking for. Per-file technologies are on the files rung, and only there: ask for it when you need to know what a matched file uses. One of: repos | files | snippets | lines.
path body string no Only match files whose path contains this. ANDed against the WHOLE query, unlike the in-query filter of the same name, which binds only to the term beside it.
file body string no Only match files whose name contains this. ANDed against the WHOLE query, unlike the in-query filter of the same name, which binds only to the term beside it.
language body string no Only match files in this language, as scc names it. ANDed against the WHOLE query, unlike the in-query filter of the same name, which binds only to the term beside it.
include_ext body array of string no Only match files with these extensions, without the dot. ANDed against the WHOLE query, unlike the in-query filter of the same name, which binds only to the term beside it.
technology body string no Only match files that use this technology, as the detector names it — e.g. "Django", "AWS SDK (Boto3)". The names in the technologies facet on this response are exactly the accepted values. It is the named form of the tech: filter and composes with it. Attribution comes from the analysis pass, so a repository whose deep pass has not finished reports a gap rather than dropping out silently.
repo body string no Restrict the search to one repository, by identifier or by unique short name. Narrows within what the principal may already see; it can never widen it.
case_sensitive body boolean no false Match case exactly. Off means case-insensitive.
code_filter body string no Restrict matching to one structural part of a file. only-declarations is jump-to-definition and only-usages is find-callers; both are far cheaper than reading files and are the right first call when you know which you want. One of: only-code | only-comments | only-strings | only-declarations | only-usages.
repositories body array of string no Restrict the search to these repositories, by identifier or unique short name. The plural form of repo, and like it, it can only narrow what the principal may already see.
context_lines body integer no 2 How many lines either side of a match to return. Zero means none: the matching lines and nothing around them. Matches whose windows overlap or touch share one hunk rather than repeating each other's lines.
context body integer no Alias for context_lines, named as cs names it. With granularity=lines and context=0 this is cs's grep mode exactly.
line_limit body integer no 500 With granularity=lines, the total number of matching lines to return across all files. The rung is exhaustive by design, so this is the bound that keeps it usable.
max_results body integer no 50 1000 Cap on returned files. The total is reported before this cap, so you can tell a truncated answer from a complete one. It is a default a caller may raise, up to a hard maximum of 1000 — raise it to 50-100 for broad discovery, and page past the maximum with offset rather than asking for more.
offset body integer no 0 How many of the ranked files to skip before listing. With max_results it is a page: offset 0 is the first page, offset 20 with max_results 20 is the second. Counted in FILES, like files_total and unlike total, which counts matches. It is an offset and not a cursor: each request is a fresh query, so if a repository is refreshed between two pages a file may be seen twice or missed once — page numbers stay meaningful, and no result set is pinned per caller. An offset past the end is an empty files array and not an error, because files_total is the only way to know where the end was and it may move. It does not change total, files_total, technologies or languages, all of which describe the whole match set. Accepted at granularity files and snippets, which list files; refused at repos and lines, whose rows are repositories and matching lines rather than files.
max_matches_per_file body integer no 5 Cap on matches returned within one file.
backend_detail body boolean no false Name the search backend for EVERY repository searched rather than only for the ones that contributed results. A diagnostic switch: the response then carries one entry per repository this principal may see, which on a large estate is most of the answer. Leave it off unless you are working out why a query was slow.

Response fields:

Field Type Description
total integer Matches across every repository searched, BEFORE max_results truncation, and counting only repositories this principal may see.
files_total integer Matching FILES, before max_results truncation. Different from total, which counts matches: one file holding forty matches is one entry in files. This is the number to page on, and total is the number to quote. When truncated is true it is a lower bound.
granularity string The rung this response was shaped at, echoed so a caller can tell a defaulted answer from one it asked for.
elapsed_ms integer How long the SEARCH took, in milliseconds: the query across every repository this principal may see, whichever backend answered for each. It excludes authorisation, the technology filter, the facet counts and serialisation, so it is a lower bound on the time a client waited and a statement about the engine rather than about the request. Compare it across runs of the same query to see a regression; do not compare it across granularities, which read different amounts.
repositories array With granularity=repos, one entry per matching repository and nothing else. Absent at every other rung.
  repository string The repository identifier.
  name string Its human-facing name.
  matches integer How many matches it holds.
  files integer How many of its files matched.
repositories_total integer With granularity=repos, how many repositories matched before max_results truncation. Absent at every other rung. This is the rung's own denominator: total counts MATCHES and files_total counts FILES, so neither of them is the number to quote beside a census of repositories. It is exact whether or not truncated is true: fewer repositories rows than this means max_results cut the list, and nothing else does.
lines array With granularity=lines, every matching line with its context. Absent at every other rung.
  repository string The repository identifier this result came from.
  commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
  path string Path relative to the repository root.
  line integer 1-based line number.
  content string The matching line.
  context_before array of string Lines above.
  context_after array of string Lines below.
offset integer The offset this page was read at, echoed so a paging caller can check what it got rather than what it asked for.
truncated boolean Files matched beyond the window this response returned — beyond offset+max_results, or beyond line_limit at granularity=lines. Compare total against what was returned to see by how much. On the last page of a paged read it is false, so it says "there is more after this" rather than "this query matched more than one page". At granularity=repos it is ALSO true when the census tally bound was reached, which cuts each row's files and matches counts rather than the list and is stated as a complete_results gap; whether the list itself was cut is repositories against repositories_total.
files array The matching files, ranked across repositories. Present at granularity files, snippets and lines; at files the hunks array is empty, because that rung returns no content.
  repository string The repository identifier this result came from.
  commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
  path string Path relative to the repository root.
  language string The language scc detected.
  score number Ranking score. Higher is better.
  total_matches integer Matches in this file before max_matches_per_file truncation.
  technologies array of string What THIS file uses, as the detector names it — the join the technologies facet beside it cannot make, because that facet counts over every file the query matched and this response lists one page. Present at granularity=files only, and empty rather than absent when nothing is attributable to the file: an empty array says the analysis pass looked and found nothing, where an absent one would say it was not asked. Pass one back as technology to narrow to the files using it.
  hunks array The returned matches as contiguous runs of lines, the way grep -A/-B prints them. Read this one. Matches whose context windows overlap or touch share a hunk, so no line is ever returned twice; matches far enough apart are separate hunks, so a gap between two hunks means lines were skipped and the code is NOT contiguous.
    start_line integer 1-based line number of the first line in lines.
    end_line integer 1-based line number of the last line in lines.
    lines array of string The hunk's lines in file order, without their terminators. The line number of lines[i] is start_line + i.
    match_lines array of integer The line numbers within this hunk that the query matched, ascending. Absolute file line numbers, not offsets into lines.
    match_ranges array WHERE within those lines the query matched, from the search engine rather than from re-scanning the line for the query's words — so a phrase, a fuzzy term and a case-folded term are all marked exactly where they hit. Each entry names its own line, so this is never read positionally against match_lines. match_lines stays the authority on WHICH lines matched: a matching line may be absent here, which means the match has no reportable offset and not that the line did not match. At most 100 ranges are reported for one line; total_matches is the count.
      line integer Absolute file line number, one of match_lines.
      ranges array The [start, end) BYTE offsets into that line's text, ascending, end exclusive. Bytes and not characters, so a line with multi-byte UTF-8 above the match is still indexed correctly. Two ranges may overlap where two query terms do.
backends object How many repositories each search backend answered for, keyed by backend name — the backend is chosen per repository at query time, so a single query routinely uses more than one. The counts SUM to the repositories searched, which is every repository this principal may see after repo and repositories narrowed it. It is an aggregate rather than a row per repository because a row per repository grows with the estate and not with the answer: on 3,000 repositories that was roughly 174 KB on every query, most of a tool call's whole byte budget, spent restating a repository list that list_repositories already answers. For the repositories that actually contributed, read backends_by_repository.
backends_by_repository object Which backend answered for each repository that CONTRIBUTED results, keyed by repository identifier — the set anybody debugging a slow or surprising answer is asking about. Bounded by the matches the search SCANNED rather than by the estate or by this page: up to the first 5000 matching files, so it can name repositories whose results are past max_results and not on this page. Pass backend_detail=true to get every repository searched instead. Present and empty when nothing matched.
technologies object Detected technology to the number of MATCHING FILES using it, counted over every file this query matched and not over the estate — it says what THESE results are built on, so every entry narrows to at least one of them when passed back as tech:. A technology no matching file uses is absent rather than zero. Counted over the whole match set rather than over the files this response lists, so it does not change with max_results. It IGNORES tech: and honours every other filter, so passing an entry back as tech: — which REPLACES the selection rather than adding to it — returns exactly the count shown, and a technology already selected does not hide the alternatives to it. Present at granularity files and snippets, which are the rungs that list files; absent at repos and lines. For the estate-wide rollup, call estate.
languages object Language to the number of MATCHING FILES scc detected as that language — so the count equals files_total when that language is passed back as the language parameter. Counted over the files this response lists over, which is every file the query matched with EVERY filter applied including language itself: unlike technologies above, a request that already names a language reports only that language here. An object, unlike the languages array on estate and repo, which is a rollup of lines and complexity rather than a facet over a result set. Present at granularity files and snippets; absent at repos and lines.
notices array of string Statements that change what this answer MEANS, and on this endpoint that is why a zero is a zero. Terms are ANDed, so one word that matches nothing returns nothing — and a bare 0 does not say which word it was. When a query of plain terms matches no file, this names the three causes apart: some terms matched nothing (drop them), no term matched anything (the estate does not contain this), or every term matched but no one file holds them all (ask for fewer words, or quote a phrase). It carries a per-term count of the FILES that hold each word, counted over the repositories you can search and under the filters in force. Present and EMPTY on any populated answer, and on a zero the engine cannot take apart — a facet-only browse, or a query using OR, NOT or a regular expression. It is not a gap: nothing was skipped and nothing was unanswerable, so the zero is a complete and correct answer that was simply missing its reason. DO NOT REPORT "no such code exists" off an empty result that carries one of these sentences.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: Find the retry helper, declarations only

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  -X POST \
  -H 'Content-Type: application/json' \
  -d '{"query": "retry backoff", "code_filter": "only-declarations", "max_results": 5}' \
  "$SEARCHCODE_URL/api/v1/search"
{
  "total": 2,
  "files": [
    {
      "repository": "example__deploy-tool",
      "commit_sha": "4f0c2e1b9a7d…",
      "path": "retry.go",
      "language": "Go",
      "score": 41.2,
      "total_matches": 1,
      "hunks": [
        {"start_line": 11, "end_line": 13,
         "lines": ["// RetryWithBackoff retries with exponential backoff.",
                   "func RetryWithBackoff(ctx context.Context, attempts int) error {",
                   "\tvar err error"],
         "match_lines": [12],
         "match_ranges": [{"line": 12, "ranges": [[5, 10], [14, 21]]}]}
      ]
    }
  ],
  "backends": {"brute-force": 22, "caisson": 8},
  "backends_by_repository": {"example__deploy-tool": "brute-force"},
  "technologies": {"Go Modules": 1},
  "languages": {"Go": 1},
  "elapsed_ms": 12,
  "notices": [],
  "gaps": []
}

POST /api/v1/find-usages

Which files use a technology, with the evidence

MCP tool: find_usages. Authentication: API key or OAuth access token

The dependency index: every file across the visible estate that uses a named technology, with the line and the matched keyword that attributed it.

Evidence is returned rather than asked to be trusted — an agent can decide whether to believe an attribution or go and read the line. Whole files are scanned, not just their first hundred lines, so a deep import is found.

Request body:

Name In Type Required Default Maximum Description
technology body string yes The technology name, as the detector reports it — e.g. "Django", "AWS SDK (Boto3)". Matched exactly and case-sensitively, so boto3 finds nothing. The detected vocabulary is the technologies map on estate_summary.
repo body string no Restrict to one repository, by identifier or unique short name.
offset body integer no 0 How many usages to skip before listing. With max_results it is a page: offset 0 is the first page, offset 50 with max_results 50 is the second. max_results is a hard maximum rather than a default, so this is the only way to reach what lies past it. It is an offset and not a cursor: each request is a fresh read, so a row added or removed between two pages may be seen twice or missed once. An offset past the end is an empty list and not an error, because total is the only way to know where the end was and it may move. It does not change total, which counts the whole set before either parameter is applied.
max_results body integer no 50 1000 Cap on returned usages. A HARD MAXIMUM of 1000 rather than a default a caller raises: a larger value is clamped to it and the response says so in gaps. total counts every usage before it bit, and offset reaches the rest.

Response fields:

Field Type Description
offset integer The offset this page was read at, echoed so a paging caller can check what it got rather than what it asked for.
usages array One entry per file that uses the technology.
  repository string The repository identifier this result came from.
  commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
  technology string The technology that was matched.
  path string Path relative to the repository root.
  language string The language scc detected.
  line integer 1-based line the evidence was found on.
  evidence string The matched keyword, so the attribution can be checked rather than trusted.
  confidence string exact or probable.
  version string The resolved version, absent when no manifest could answer. An absent version means unknown rather than none.
  version_source string lock, declared or inferred. A lock says what IS installed; a declared range says what MIGHT be, and only the first supports a vulnerability claim.
  version_confidence string exact when a lockfile pinned it and the import-to-package mapping is known, probable when either half was a convention. Separate from confidence above, which is about whether this file uses the technology at all.
  manifest_path string The manifest the version came from, relative to the repository root — "resolved from services/api/go.mod" can be opened and checked.
total integer Usages found before max_results truncation. Compare it against the length of usages to see how much was left out.
truncated boolean More usages matched than max_results returned. False means usages holds every match, so total is the whole answer rather than a lower bound.
notices array of string Sentences that change what this answer MEANS, above the data rather than beside it. Present and empty on an answer that needs none. A zero carries one when the detector declares the technology you named and no file you can see uses it: that is a COMPLETE answer about the code, and it is deliberately not a gap, because a gap says the question could not be answered. A name no detector rule declares is the opposite case and appears in gaps instead — the zero then says something about the spelling and nothing about the estate.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: Everything that imports boto3

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  -X POST \
  -H 'Content-Type: application/json' \
  -d '{"technology": "AWS SDK (Boto3)", "max_results": 3}' \
  "$SEARCHCODE_URL/api/v1/find-usages"
{"usages": [{"repository": "example__data-pipeline", "commit_sha": "7c9a...", "technology": "AWS SDK (Boto3)", "path": "app/deep_import.py", "language": "Python", "line": 154, "evidence": "boto3", "confidence": "probable"}], "total": 1, "truncated": false, "notices": [], "gaps": []}

GET /api/v1/findings

Code quality AND security findings, one surface

MCP tool: findings. Authentication: API key or OAuth access token

Both kinds of finding come from here, and kind partitions them. The hosted product splits these across two calls and merges them in the client; that split is deliberately not propagated.

The deep analysis pass trails ingestion, so a repository that has not been analysed yet appears in gaps rather than contributing zero findings.

Parameters:

Name In Type Required Default Maximum Description
repo query string no Restrict to one repository. Repository identifier, clone URL, owner/name, or a unique name suffix.
path query string no Restrict to one file, relative to the repository root. Filtered before max_results, so a file's findings are never truncated away by a large repository's.
kind query string no Which ENGINE found it: security is the secret scanner, quality is the code-quality rules, and the two sum to total. Omit for both. Not the same axis as category, which is what the rule is ABOUT — see it. One of: security | quality.
severity query string no Only findings at this severity. One of: error | warning | info.
category query string no What the rule is ABOUT, across BOTH engines. A different axis from kind, and they collide on one word: category=security INCLUDES the quality rules that are about security, so it is a superset of kind=security and is the larger of the two — measured on one estate, 6,687 against 699. One of: accessibility | concurrency | correctness | deprecated | maintainability | modernization | performance | safety | security.
tests query string no exclude Findings in TEST files — test.go, test/, tests/, spec/, tests/, testdata/, *.test.ts, *.spec.js, *Test.java, *Tests.cs, test.py, conftest.py, *_spec.rb and the like, by one definition both products share. exclude, the default, leaves them out of findings and total and counts them in tests_excluded; include returns every finding, each carrying test; only returns just the test-file ones. They are kept, not deleted: a real credential committed in a test fixture is still a real credential, and include reaches it. One of: exclude | include | only.
offset query integer no 0 How many findings to skip before listing. With max_results it is a page: offset 0 is the first page, offset 50 with max_results 50 is the second. max_results is a hard maximum rather than a default, so this is the only way to reach what lies past it. It is an offset and not a cursor: each request is a fresh read, so a row added or removed between two pages may be seen twice or missed once. An offset past the end is an empty list and not an error, because total is the only way to know where the end was and it may move. It does not change total, which counts the whole set before either parameter is applied.
max_results query integer no 50 1000 Cap on returned findings. A HARD MAXIMUM of 1000 rather than a default a caller raises: a larger value is clamped to it and the response says so in gaps. total counts every finding before it bit, and offset reaches the ones past this page.

Response fields:

Field Type Description
offset integer The offset this page was read at, echoed so a paging caller can check what it got rather than what it asked for.
findings array The findings, most severe first.
  repository string The repository identifier this result came from.
  commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
  path string Path relative to the repository root.
  line integer 1-based line the finding is on.
  rule string The rule identifier that fired. A security rule's id comes from the betterleaks rule set vendored into this binary and is stable for the life of a release; a quality rule's is this product's own. A trailing .N — 69 of the 468 security rules carry one — numbers the TOKEN FORMATS one vendor issues, not revisions of a rule: pinecone-api-key.1 is the UUID format and pinecone-api-key.2 the pcsk one, and they carry different confidences and therefore different severities. So FILTER ON THE WHOLE ID, never on the stem before the suffix: a format added upstream arrives as a new sibling, and a stem match would silence or count a rule nobody has looked at.
  kind string Which engine found it: security or quality.
  category string What the rule is about. A quality finding may be category security.
  severity string error, warning or info.
  message string What the rule found, in prose.
  test boolean The finding is in a test file. Only ever true under tests=include or tests=only, because the default leaves test-file findings out. Read from the path, so it is the same answer for a finding however long ago it was analysed.
  cwe string The weakness class the RULE looks for, as CWE-89. Only on a security finding — a secret, or a quality rule whose category is security — and absent on every other finding by scope, not because it has no weakness. Absent too on a security finding whose rule names no single weakness class. The label is never quotable on its own: whenever any row carries one, the first entry of notices says what it does and does not claim, and a CWE count from this product is not comparable with one from a data-flow tool.
  cwe_fit string Present with cwe. exact: the matched text is itself the weakness. candidate: the text is the operation the CWE names, and whether it is the weakness depends on something the pattern cannot see, such as whether untrusted input reaches it or whether a matched secret is live.
  detection string Present with cwe. Always pattern: a lexical match with no data flow or taint analysis.
  rule_precision string Present with cwe. The rule's measured precision and where it was measured, or unmeasured. It is the rule's, not this finding's.
  owasp string The OWASP Top 10 2021 category OWASP's own published mapping places cwe in, as A03:2021. Absent when that mapping does not place it, rather than a guessed category.
tests_excluded integer Findings that matched every other filter and were left out because they are in test files and tests was exclude, the default. Zero under include and only. When it is not zero, notices says so in a sentence as well.
total integer Findings matching this query before max_results truncation, across every repository this principal may see. It is the same number the estate summary reports for the same filter, and it is NOT the length of findings — compare the two to see how much a cap left out.
truncated boolean More findings matched than max_results returned. False means findings holds every match at this filter, which is the only state in which the answer can be quoted as complete.
notices array of string Statements that change what these findings MEAN, and which a consumer must show its reader. Three can appear here. A repository whose stored analysis was produced by an earlier version of the secret-scanning rule set says so, says that a re-analysis is queued, says that findings may be ADDED and none removed when it completes, and says how many repositories are still pending — the answer is complete under the rules that produced it, so it is NOT a gap. A repository that has not been analysed at all says so in as many words, and says that this is not a statement that it is clean; that one is also reported in gaps, which names which repositories. The last is ALWAYS present whenever security findings are in scope: every finding is read from the current checkout at HEAD, so a secret that was committed and later removed is still in the git history and is not reported. Asking for kind=quality is the only thing that suppresses it.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: Security findings only

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/findings?kind=security&max_results=2"
{"findings": [{"repository": "example__billing-service", "commit_sha": "9b1c...", "path": "deploy/signing.pem", "line": 1, "rule": "private-key", "kind": "security", "category": "security", "severity": "error", "message": "Identified a Private Key, which may compromise cryptographic security and sensitive data encryption.", "test": false, "cwe": "CWE-321", "cwe_fit": "candidate", "detection": "pattern", "rule_precision": "unmeasured", "owasp": "A02:2021"}], "tests_excluded": 0, "total": 1, "truncated": false, "notices": ["About the weakness labels: a CWE here names the weakness class the RULE looks for ...", "Findings come from the current checkout at HEAD. A secret committed earlier and since removed is still in the git history and is NOT reported here."], "gaps": []}

Estate

GET /api/v1/repos

List the repositories this principal can reach

MCP tool: list_repositories. Authentication: API key or OAuth access token

Every repository the calling principal may search, with its size in lines and bytes and the capabilities each source tier can answer for. A repository that is not listed is either absent from the estate or invisible to this principal; the two are deliberately indistinguishable.

Rank the estate from this one call rather than analyzing each repository: lines and bytes come off the same inventory pass analyze reads, and include=languages adds the per-language rollup when the question is what the code is written in rather than where it is. A row whose inventory pass has not run carries neither field, and the gap on this response names it — absent is not zero.

Read the capabilities before asking a history question. A directory source has no commits, so blame and log over one are gaps rather than empty answers.

Parameters:

Name In Type Required Default Maximum Description
group query string no Only repositories in this repository group.
label query string no Only repositories carrying this label. Labels are categorisation and grant nothing.
name_like query string no Only repositories whose name CONTAINS this, case-insensitively. A substring match and deliberately not a glob or a regex: the question this answers is "which repositories are called something like kai", and a caller that guesses at kai* versus *kai* gets the same answer either way. Matched against the name and the identifier, so a trailing segment works whichever form the caller has. Use it instead of searching the estate for a name — a content search pays index cost to answer a directory question.
include query string no Add the per-language breakdown to every repository row, the same array analyze returns for one repository. OFF BY DEFAULT and opt-in deliberately: lines and bytes are two integers a row and are always there, and this is a variable-length array whose size is the number of languages a repository is written in — on a large estate that is the difference between a listing an agent can hold and one it truncates. Ask for it when the question is what the estate is written in; leave it off when the question is where the code is. One of: languages.
offset query integer no 0 How many repositories to skip before listing. With max_results it is a page: offset 0 is the first page, offset 100 with max_results 100 is the second. It is an offset and not a cursor: each request is a fresh read, so a repository added or retired between two pages may be seen twice or missed once. An offset past the end is an empty repositories array and not an error. It does not change total, which describes the whole filtered set.
max_results query integer no 100 1000 Cap on repositories returned in one page. total is reported before this cap, so a truncated answer is distinguishable from a complete one. It is a default a caller may raise, up to a hard maximum of 1000. An estate larger than that is read with offset rather than in one request. The default is a page rather than everything because an estate of a few thousand repositories is one response no client renders and no model reads to the end of.

Response fields:

Field Type Description
repositories array The visible repositories, sorted by name. This page only: offset and max_results decide which slice of total it is.
  id string Stable identifier, derived from the clone URL — github.com__owner__name. Accepted wherever {repo} appears.
  name string Human-facing name, usually owner/name.
  source string git or directory.
  commit_sha string The checked-out commit, or "unavailable" for a directory source.
  groups array of string The repository groups authorisation is granted over.
  labels array of string Categorisation and facets. They grant nothing.
  phase string cloning, ready, no_content, empty, skipped or failed. A repository still cloning is answerable for nothing yet; no_content cloned and walked cleanly and kept no files, so it is not searchable and is not a failure.
  files_indexed integer How many files the corpus walk kept for this repository. ZERO is the answer this field exists for: a repository whose every file was excluded contributes nothing to search, findings or the technology catalogue, and before this field the only way to find one was to read every repository's state off disk. It is a FILE count and does not balance against the admin endpoint's files_skipped, which counts skip EVENTS — a skipped directory is one event whatever it holds.
  lines integer Total lines the inventory pass measured, the same number analyze returns for this repository. ABSENT rather than zero when that pass has not reached it — a zero here would rank an unmeasured repository as a small one, and the inventory gap on this response names every row it is missing from. Use it with bytes to decide where the code is before deciding what to read: files_indexed counts FILES, so five files of generated JSON report the same 5 as five files of source.
  bytes integer Total source bytes the inventory pass measured, the same number analyze returns. Absent on the same terms as lines, and the better of the two for ordering an estate a language at a time: lines varies by an order of magnitude between languages and bytes does not.
  languages array The per-language rollup, largest first — ONLY when include=languages was asked for, and absent otherwise. The same array analyze returns for one repository, so 'what is the estate written in, by repository' is one call rather than one call per repository.
    language string scc's name for the language.
    files integer Files in it.
    lines integer Lines in it, including blanks and comments.
    code integer Lines of code in it.
    complexity integer Summed cyclomatic complexity.
  refresh_interval string How often this repository is fetched, as a duration string ("1h0m0s"). It is the repository's own setting where it has one and indexing.refreshInterval otherwise, and it is what skip_reason's "checked again on the ordinary refresh interval" means. Absent when it is not known, never zero: a zero on a sentence about waiting reads as "constantly".
  skip_reason string Why this repository is not ready, in ingestion's own words — over the configured size limit, a remote with no commits, or the error a clone that has never succeeded last failed with. ABSENT rather than empty when there is none, because a ready repository was not skipped and an empty string in a field you render reads as a reason that says nothing. It is the same sentence the absence gap on this response carries, exposed as a field so it does not have to be parsed back out of one.
  capabilities object Which questions this source can answer: search, dependencies, findings, history, blame, coupling, commit_sha.
repository_groups array Every repository group this principal can see something in, sorted by name. A repository group is the unit authorisation is granted over, so this is the vocabulary the group parameter above accepts — and it is deliberately NOT narrowed by the group or label parameters, so a caller that has already filtered can still see what else it could ask for.
  name string The group name, as it appears in each repository's groups.
  description string The operator's description of what the group is for. Empty when none was configured, which is ordinary.
  repositories integer How many visible repositories are in this group. Counted over everything visible, so it is what you get by asking for this group.
total integer How many repositories match the filters, BEFORE max_results truncation. Compare it against the length of repositories to tell a complete answer from a page of one.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: What can this credential reach?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/repos"
{"repositories": [{"id": "example__docs-drop", "name": "example/docs-drop", "source": "directory", "commit_sha": "unavailable", "groups": ["example-group"], "labels": [], "phase": "ready", "capabilities": {"search": true, "dependencies": true, "findings": true, "history": false, "blame": false, "coupling": false, "commit_sha": false}}, {"id": "example__monorepo", "name": "example/monorepo", "source": "git", "commit_sha": "unavailable", "groups": ["example-group"], "labels": [], "phase": "skipped", "skip_reason": "the repository is larger than maxRepoSizeMB", "capabilities": {"search": true, "dependencies": true, "findings": true, "history": true, "blame": true, "coupling": true, "commit_sha": true}}], "repository_groups": [{"name": "example-group", "description": "Whatever you pointed this instance at", "repositories": 2}], "total": 2, "gaps": [{"capability": "history", "reason": "source type 'directory' has no history", "sources": ["example__docs-drop"], "source_count": 1, "kind": "unanswered"}, {"capability": "search", "reason": "the repository was not ingested: the repository is larger than maxRepoSizeMB", "sources": ["example__monorepo"], "source_count": 1, "kind": "unanswered"}]}

GET /api/v1/repos/{repo}/analyze

One repository's inventory, languages, technologies and findings rollup

MCP tool: analyze. Authentication: API key or OAuth access token

The overview an agent reads before it reads any code: how big the repository is, what it is written in, what it depends on and what the analysis pass found.

The deep pass trails the inventory pass, so a repository that has just been ingested answers with complete language totals and a gap for findings and technologies rather than with zeroes. Check gaps before concluding a repository has no findings: an unanalysed repository and a clean one produce the same empty numbers, and only the gaps array tells them apart.

Parameters:

Name In Type Required Default Maximum Description
repo path string yes Repository identifier, clone URL, owner/name, or a unique name suffix.

Response fields:

Field Type Description
repository string The repository identifier.
name string The repository's human-facing name.
commit_sha string The commit analysed, or "unavailable".
files integer Files the inventory pass counted.
lines integer Total lines across those files.
code integer Lines of code, excluding comments and blanks.
comment integer Comment lines.
blank integer Blank lines.
bytes integer Total bytes across analysed files.
complexity integer Summed cyclomatic complexity estimate.
languages array Per-language totals, largest first.
  language string The language name, as scc reports it.
  files integer Files in this language.
  lines integer Lines in this language.
  code integer Code lines in this language.
  complexity integer Complexity in this language.
technologies object Detected technology to the number of files using it. Empty with a gap present means the deep pass has not finished, not that nothing was detected.
findings object Finding counts: total, by_severity, by_category and by_kind.
  total integer All findings in this repository OUTSIDE test files; tests_excluded counts the rest.
  by_severity object Counts keyed by severity.
  by_category object Counts keyed by what the rule is ABOUT. A different axis from by_kind: its security bucket INCLUDES the quality rules that are about security, so it is larger than by_kind's.
  by_kind object Counts keyed by which ENGINE found it: security is the secret scanner, quality is the code-quality rules, and the two sum to total. by_kind's security is a SUBSET of by_category's.
  tests_excluded integer Findings in TEST files, which every count beside this one leaves out so that the headline is production code's. They are kept, not deleted: the findings endpoint's tests=include or tests=only lists them. When it is not zero, notices says so in a sentence as well.
notices array of string Statements that change what these findings MEAN, and which a consumer must show its reader. Three can appear here. A repository whose stored analysis was produced by an earlier version of the secret-scanning rule set says so, says that a re-analysis is queued, says that findings may be ADDED and none removed when it completes, and says how many repositories are still pending — the answer is complete under the rules that produced it, so it is NOT a gap. A repository that has not been analysed at all says so in as many words, and says that this is not a statement that it is clean; that one is also reported in gaps, which names which repositories. The last is ALWAYS present whenever security findings are in scope: every finding is read from the current checkout at HEAD, so a secret that was committed and later removed is still in the git history and is not reported. Asking for kind=quality is the only thing that suppresses it.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: Overview before reading any code

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/repos/billing-service/analyze"
{"repository": "example__billing-service", "name": "example/billing-service", "commit_sha": "9b1c...", "files": 3, "lines": 214, "code": 168, "comment": 22, "blank": 24, "bytes": 5120, "complexity": 11, "languages": [{"language": "Go", "files": 3, "lines": 214, "code": 168, "complexity": 11}], "technologies": {"Go Modules": 1}, "findings": {"total": 2, "by_severity": {"warning": 2}, "by_category": {"correctness": 2}, "by_kind": {"quality": 2}, "tests_excluded": 0}, "notices": ["Findings come from the current checkout at HEAD. A secret committed earlier and since removed is still in the git history and is NOT reported here."], "gaps": []}

GET /api/v1/estate

Totals across everything this principal can see

MCP tool: estate_summary. Authentication: API key or OAuth access token

The estate-wide rollup: repositories, files, lines, languages, technologies and findings, summed over the repositories this principal may search and no others.

An aggregate is where authorisation leaks most quietly — a count is not obviously a disclosure until somebody notices the number moves when a repository they cannot read gains a file. Coverage says how much of the estate the deep pass has finished.

findings.by_kind and findings.by_category are TWO DIFFERENT AXES and they appear here eight keys apart. by_kind counts which ENGINE produced a finding — security is the secret scanner, quality is the code-quality rules — and the two sum to total. by_category counts what the RULE IS ABOUT, across both engines, and the two axes collide on the word security: dozens of quality rules are about security, so by_category's security bucket INCLUDES them and is the larger of the two. Measured on one 434-repository estate they read 6,687 and 699. Neither is wrong; do not add them, and do not quote one as the estate's security posture without saying which it is.

Parameters:

Name In Type Required Default Maximum Description
max_results query integer no 15 1000 How many languages and how many technologies to return, each largest-first. It bounds BOTH lists, because both have the same long tail on a real estate: one measured 88 languages and roughly 300 technologies, most of the latter used by one or two files. languages_total and technologies_total carry the true counts whatever this is set to, so a capped answer is never mistaken for the whole estate. There is no offset: both populations are far below the ceiling, so raising this reaches all of them.

Response fields:

Field Type Description
repositories integer Repositories visible to this principal.
files integer Files across those repositories.
lines integer Lines across those files.
code integer Code lines, excluding comments and blanks.
bytes integer Bytes across analysed files.
complexity integer Summed complexity estimate.
languages array Per-language totals across the estate.
  language string The language name.
  files integer Files in this language.
  lines integer Lines in this language.
  code integer Code lines in this language.
  complexity integer Complexity in this language.
languages_total integer How many distinct languages the visible estate holds, whatever max_results returned. A cap with no denominator is how an agent reports that the estate uses fifteen languages.
technologies object Technology name to the number of files using it, the most-used max_results of them.
technologies_total integer How many distinct technologies the visible estate holds, whatever max_results returned.
notices array of string Statements that change what this answer MEANS. One appears when the technology list was capped, because the response states the language cap in its own counted sentence and one response can carry only one of those. The other is always present: the findings counts here are read from each repository's current checkout at HEAD, so a secret that was committed and later removed is still in the git history and is not counted.
findings object Estate-wide finding counts.
  total integer All findings across the visible estate OUTSIDE test files; tests_excluded counts the rest.
  by_severity object Counts keyed by severity.
  by_category object Counts keyed by what the rule is ABOUT. A different axis from by_kind: its security bucket INCLUDES the quality rules that are about security, so it is larger than by_kind's.
  by_kind object Counts keyed by which ENGINE found it: security is the secret scanner, quality is the code-quality rules, and the two sum to total. by_kind's security is a SUBSET of by_category's.
  tests_excluded integer Findings in TEST files, which every count beside this one leaves out so that the headline is production code's. They are kept, not deleted: the findings endpoint's tests=include or tests=only lists them. When it is not zero, notices says so in a sentence as well.
coverage object How much of the visible estate the deep analysis pass has finished.
  repositories integer Visible repositories.
  analysed integer Those whose deep pass has completed.
  pending integer Those still waiting for it.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: How big is what I can see?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/estate"
{"repositories": 3, "files": 12, "lines": 640, "code": 500, "bytes": 18240, "complexity": 31, "languages": [{"language": "Go", "files": 9, "lines": 500, "code": 400, "complexity": 25}], "languages_total": 1, "technologies": {"Go Modules": 3}, "technologies_total": 1, "notices": ["Findings come from the current checkout at HEAD. A secret committed earlier and since removed is still in the git history and is NOT reported here."], "findings": {"total": 4, "by_severity": {"warning": 4}, "by_category": {"correctness": 4}, "by_kind": {"quality": 4}, "tests_excluded": 0}, "coverage": {"repositories": 3, "analysed": 2, "pending": 1}, "gaps": [{"capability": "findings", "reason": "the deep analysis pass has not finished for this repository", "sources": ["example__docs-drop"], "source_count": 1, "kind": "unanswered"}]}

Operations

GET /api/v1/repos/{repo}/complexity

Cyclomatic complexity for EVERY file in a repository, ranked by nothing

MCP tool: file_complexity. Authentication: API key or OAuth access token

The complete per-file complexity map, which is what a tool needs to decide which files to read into a bounded context window: complex files explain a system, and a top-20 list cannot drive that choice for a repository of two thousand files.

RANKED BY NOTHING. Rows come back in path order so that the caller does its own ranking, and so that paging is stable — a ranking this endpoint chose would be one the caller then has to undo. If you want risk rather than complexity, hotspots ranks complexity × churn and is a different question.

NO COMMIT HISTORY IS REQUIRED. These numbers come from the inventory pass, which reads the working tree, so a directory source and a depth-1 clone answer exactly as a full clone does. Nothing here is degraded by a shallow clone.

The corpus is the one search reports: a file excluded by indexing.exclude or by the built-in exclusion floor appears in neither the rows nor files_total.

PAGE IT with offset and max_results, and read commit_sha on every page — the listing describes one revision, so a page whose commit_sha differs from the previous page's was read after a refresh and the two do not compose into one map.

Parameters:

Name In Type Required Default Maximum Description
repo path string yes Repository identifier, clone URL, owner/name, or a unique name suffix.
offset query integer no 0 How many files to skip before listing. With max_results it is a page: offset 0 is the first page, offset 500 with max_results 500 is the second. It is an offset and not a cursor: each request is a fresh read, so if the repository is refreshed between two pages a file may be seen twice or missed once — which is what commit_sha on the response is for. An offset past the end is an empty files array and not an error. It does not change files_total, which describes the whole corpus.
max_results query integer no 500 1000 Cap on rows returned in one page. files_total is reported before this cap, so a truncated answer is distinguishable from a complete one. It is a default a caller may raise, up to a hard maximum of 1000. A repository with more rows than that is read with offset rather than in one request.

Response fields:

Field Type Description
repository string The repository identifier this result came from.
commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
repository_name string The repository's own name — its normalised clone URL, or its path for a directory source. This is what to show a person; repository is the identifier to pass back.
files array One row per file in the corpus, in path order. This page only: offset and max_results decide which slice of files_total it is.
  path string Path relative to the repository root.
  language string The language scc detected.
  complexity integer The file's cyclomatic complexity estimate. Zero is a real answer and means the file has no branching — a JSON document, a licence, a straight-line script — and never that it was not measured; a file that was not measured has no row at all.
  lines integer Total lines in the file.
  code integer Lines of code, excluding comments and blanks.
  comment integer Comment lines.
  blank integer Blank lines.
  bytes integer The file's size in bytes.
files_total integer Files in the corpus, counted BEFORE offset and max_results. It is the denominator a caller pages against and the number to compare the row count with; excluded files are in neither.
offset integer The offset this page was read at, echoed so a paging caller can check what it got rather than what it asked for.
truncated boolean Files remain after this page — files_total is greater than offset plus the rows returned. False on the last page, so it says "there is more after this" rather than "this repository has more than one page".
notices array of string Statements that change what this answer MEANS rather than saying a question went unanswered. Empty on the first page; a page past it carries one naming the revision it describes, because each page is a fresh read and pages read at different commits do not compose into one map.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: The complete complexity map, first page

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/repos/billing-service/complexity?max_results=2"
{"repository": "example__billing-service", "commit_sha": "9b1c...", "repository_name": "https://example.invalid/billing-service", "files": [{"path": "invoice.go", "language": "Go", "complexity": 4, "lines": 82, "code": 64, "comment": 8, "blank": 10, "bytes": 1937}, {"path": "orders.go", "language": "Go", "complexity": 6, "lines": 74, "code": 60, "comment": 5, "blank": 9, "bytes": 1704}], "files_total": 3, "offset": 0, "truncated": true, "notices": [], "gaps": []}

GET /api/v1/repos/{repo}/hotspots

Where the change activity is, weighted by complexity

MCP tool: hotspots. Authentication: API key or OAuth access token

Complexity multiplied by churn, over a window. A file with complexity 500 that has not changed in a year is probably stable infrastructure; a simpler file changed forty-seven times in ninety days is where the work is. Complexity alone ranks the first and churn alone ranks trivial-but-busy files, so both inputs are returned beside the score.

REQUIRES HISTORY. A directory source has no commits and a shallow clone has few, so both answer with a populated gaps array rather than an empty ranking — "no history here" and "nothing has changed here" are different statements.

A DORMANT REPOSITORY IS STILL RANKABLE. Complexity comes from the code and churn comes from the commit walk; neither depends on the calendar. If no commit falls inside the last days days, the same window is re-anchored to this repository's NEWEST COMMIT and you get a real complexity × churn ranking of the period it was last worked on — which is the useful answer when deciding whether to adopt, fork or rewrite a finished dependency. anchor says which window produced the rows and window_start says when it began, so READ THEM: a re-anchored answer may be about 2019. Pass anchor=today to refuse it. Only when no window of either anchor scores anything does the ranking collapse to complexity alone, and the response says so.

The walk behind this runs after each index, never per request, so the answer describes the commit named in the response rather than whatever the branch is at this second.

Parameters:

Name In Type Required Default Maximum Description
repo path string yes Repository identifier, clone URL, owner/name, or a unique name suffix.
days query integer no 90 Churn window in days. days accepts 0 to 365. 0 disables churn and ranks by complexity alone, which the response states. Omit to use the instance's configured window. The ceiling is not a limit on how old a repository can be ranked: a window that catches nothing re-anchors to the newest commit, so there is never a reason to ask for a wider one.
anchor query string no auto What the churn window is measured back from. auto uses today and re-anchors to this repository's newest commit if that catches nothing. today is the calendar window and never re-anchors — ask for it when the question is where risk is NOW, and accept a complexity ranking on a dormant repository. last-commit always measures back from the newest commit, which answers "which files churned most while this was being built". The response reports which one produced the rows. One of: auto | today | last-commit.
limit query integer no 20 1000 How many files to rank.

Response fields:

Field Type Description
repository string The repository identifier this result came from.
commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
window_days integer The churn window that was applied. 0 means churn was disabled.
churn boolean Whether churn contributed to the ranking. False means the scores are complexity alone — either because you passed days=0, or because no window scored anything. It is read off anchor: a re-anchored ranking is still a churn ranking, so this is true there even though churn_fell_back is too.
anchor string What the window that produced these rows was measured back from: today, last-commit, or none when no window was applied. READ THIS BEFORE TREATING THE ROWS AS RECENT — window_days says how long the window was and this says which window it was.
window_start string RFC 3339 timestamp of the beginning of that window. Empty when anchor is none. "90 days" says nothing about which 90 once the anchor can move, so this is what makes a re-anchored answer checkable.
commits_in_window integer How many of the scanned commits CONTRIBUTED TO THESE SCORES. This is the ranking's denominator, counted over the window anchor names — zero means every row's score is its complexity alone, which is also what days=0 reports because no commit contributed. commits_scanned beside it is how much history was read, which is a different question.
churn_fell_back boolean True when the CALENDAR window caught no commits, so this is not the ranking the window you asked for would have given. It does not mean the ranking is complexity alone: the first fallback re-anchors the same window to the newest commit and is a real churn ranking. Read anchor to tell the two apart. A dormant repository is the ordinary cause and it is not an error: an open-source estate is mostly finished repositories. The notices array says so in words, and window_days names the window that was empty.
hotspots array Ranked files, hottest first.
  path string Path relative to the repository root.
  language string The language scc detected.
  complexity integer The file's cyclomatic complexity.
  changes integer Commits touching it inside the window.
  score integer complexity × changes, or complexity alone when churn is disabled.
  last_change string RFC 3339 timestamp of the newest commit touching this file ANYWHERE in the walked history, not only inside the window. That is what makes it the answer to "how far back was this touched" on a complexity-only ranking, where it used to be empty on every row. Empty means no walked commit touched it.
hotspots_total integer How many files scored at all, before limit trimmed the list. Raising limit returns more of these.
commits_scanned integer Commits the cached walk covered. Zero with a populated gaps array means no history was available, which is not the same as a repository nobody has changed.
truncated boolean The LIST was cut short by limit: hotspots_total files scored and fewer were returned. Raise limit for more.
commits_truncated boolean The WALK was cut short: the commit cap was reached, so older commits are not represented in any score here. No request parameter widens this — it is analysis.history.maxCommits, which the operator sets.
notices array of string Sentences a consumer must show the reader — truncation, a partial walk, churn being disabled. Prose rather than flags, because a flag a client does not render reaches nobody.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: Where is the work happening in this repository?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/repos/shop/hotspots?days=90&limit=3"
{"repository": "example__shop", "commit_sha": "9b1c…", "window_days": 90, "churn": true, "hotspots": [{"path": "orders.go", "language": "Go", "complexity": 6, "changes": 4, "score": 24, "last_change": "2026-07-30T09:12:00Z"}], "hotspots_total": 1, "commits_scanned": 66, "truncated": false, "commits_truncated": false, "notices": [], "gaps": []}

GET /api/v1/repos/{repo}/complexity-trend

Is this file getting worse?

MCP tool: complexity_trend. Authentication: API key or OAuth access token

One file's complexity measured at every commit that TOUCHED it, oldest first. Every point is a real measurement of the real blob at a real SHA — nothing is interpolated and nothing is sampled at calendar intervals, because a file untouched for six months has identical complexity at every commit in that gap.

READ THE DENSITY, NOT THE COMPLEXITY. Raw complexity rises with any growth, so a file that doubled in length and doubled in complexity has not got worse — it got bigger, at unchanged density. That is why every point carries code, lines and language beside complexity, and why the quantity that trends is complexity per 1,000 code lines. The unit is cyclomatic complexity as scc counts it, calibrated against nothing: a trend is a fact about this file, an absolute value is not a grade.

THE COMPARISON NEVER STARTS AT THE FILE'S CREATING COMMIT. Every file begins as a stub, so first-to-last says "denser" for essentially everything that has ever grown. comparison names both ends and the rule that chose them.

A RENAME IS FOLLOWED and each point records the path it was measured at, so the points before a rename are real measurements rather than the zeros a naive walk produces.

REQUIRES HISTORY. A directory source has no commits and a repository declared with fullHistory: false has one, and both answer with a gap rather than a one-point flat line — "this clone holds no history" and "this file has never changed" are different statements and only the second is about the code.

Parameters:

Name In Type Required Default Maximum Description
repo path string yes Repository identifier, clone URL, owner/name, or a unique name suffix.
path query string yes File path relative to the repository root. A trend is a question about one file, so unlike commit_log this cannot be omitted.
symbol query string no Narrow the trend to ONE function, method or constructor declared in path today: its name, or Class.name where a bare name matches more than one. Each point measures that declaration's own lines with the same scc count, and the series is FOLLOWED BACK ACROSS A SPLIT when exactly one other file lost a function of that name in the commit it arrived in — two or more and the series stops and says so rather than guess, and a function renamed in the commit that moved it is not followed. measures says which kind of number this is. A name the file does not declare is refused naming what it does declare.
since query string no Bound the walk to commits at or after this instant — an RFC 3339 timestamp, or a bare YYYY-MM-DD date. Omit for the file's whole history, which is the ordinary call: the cost is the file's own churn rather than the length of the repository's history.
max_points query integer no 200 1000 How many points to measure at most, capped at 1000. A file with more touching commits than this is DOWNSAMPLED rather than declined — every point is still a real measurement and the series still spans the file's whole history, so only the resolution drops. sampled, interval and total_commits say what happened. There is no offset because there is nothing past the end to page to.

Response fields:

Field Type Description
repository string The repository identifier this result came from.
commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
measures string What every number in this response is OF: file for the whole file, function when symbol was given. Stated on every response so a function's number is never read as its file's, or the reverse Not called scope, which hotspot_trend already uses for WHICH REPOSITORIES.
symbol string The function a function scope series is about, as it resolved today — qualified with its class when it has one. Absent for a file series.
moves array Where a function series crossed from one file to another, newest first: sha, when, from and to. Empty when it never moved, and always empty for a file series, whose renames are in renames.
  sha string The commit the function arrived in.
  when string RFC 3339 timestamp of that commit.
  from string The path it was in before.
  to string The path it arrived in.
walk_ended string For a function series, why the walk back stopped where it did: where the function was written, the start of the window, or a split with more than one candidate that the series declined to guess between. Absent for a file series.
path string The path asked for, as it is today. Each point carries the path it was measured at, which differs for anything renamed inside the window.
unit string What the numbers are: cyclomatic complexity as scc counts it, and density as complexity per 1,000 code lines. Stated on every response because an absolute density is calibrated against nothing and means nothing on its own — the trend is the answer.
points array The measurements, OLDEST FIRST, in the order git walked them rather than sorted by date: a burst of commits inside one second carries one timestamp, and a rebase makes author dates non-monotonic.
  sha string The commit this blob was read at, in full.
  when string RFC 3339 timestamp of the commit.
  path string The path the file was at IN THIS COMMIT, which is not today's path before a rename.
  language string The language scc detected in these bytes.
  lines integer Total lines in the file at this commit.
  code integer Code lines at this commit, which is the DENOMINATOR of density and the reason a growing file is not automatically a worsening one.
  complexity integer Cyclomatic complexity at this commit.
  density number complexity per 1,000 code lines. Absent for an unmeasured point and for a file with no code lines at all, because 0 is a measurement and neither of those is.
  created boolean This commit ADDED the file, so the file is a stub here — or, for a function series, the function was WRITTEN here. No comparison is made against it.
  moved_from string On a function series, the path the function was in before this commit moved it here. Absent everywhere else.
  measured boolean False for an UNKNOWN point. Never read a false one as a zero: a binary blob, a language scc does not know, a commit missing from the clone and a deletion all land here, and every one of them would draw as "simple" if it were zero.
  unknown string Why this point could not be measured. Empty when measured is true.
total_commits integer How many commits touched the file inside the window, before any downsampling. This is the denominator sampled is about.
sampled boolean The series is a downsample of total_commits. False means it is exhaustive for this window — every commit that touched the file is a point.
interval integer One point per this many touching commits. 1 means no sampling.
unknown_points integer How many points could not be measured. They are EXCLUDED from the comparison rather than counted as zero, so this is what says how much of the series the answer rests on.
unknown_reasons array of string One line per distinct cause, with how many points it accounts for. "2 unknown" alone cannot tell a shallow clone from a binary file.
renames integer How many points were measured at a path other than the one asked for. Non-zero means this file has been moved inside the window.
comparison object The movement, and the TWO POINTS it was taken between. Absent when there is none, with no_comparison saying why — a comparison that quietly used the file's creating commit would report growth as decay.
  basis string The rule that chose the baseline, in prose, so the pair is checkable rather than trusted.
  from object The baseline point: sha, when, path, language, lines, code, complexity and density, as in points.
  to object The newest measured point, in the same shape.
no_comparison string Why no movement is reported: fewer than two measurable points, or nothing to compare that is not the file's creating commit. Empty when comparison is present.
notices array of string Sentences a consumer must show the reader — the unit and method, the sampling, the unknown points and which two points the movement was taken between. Prose rather than flags, because a flag a client does not render reaches nobody.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: Is this file getting worse?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/repos/shop/complexity-trend?path=orders.go"
{"repository": "example__shop", "commit_sha": "9b1c…", "path": "orders.go", "unit": "cyclomatic complexity counted by scc; density is complexity per 1,000 code lines", "points": [{"sha": "3f2a…", "when": "2024-02-11T09:02:00Z", "path": "orders.go", "language": "Go", "lines": 40, "code": 31, "complexity": 2, "density": 64.5, "created": true, "measured": true}, {"sha": "9b1c…", "when": "2026-07-30T09:12:00Z", "path": "orders.go", "language": "Go", "lines": 620, "code": 498, "complexity": 96, "density": 192.8, "measured": true}], "total_commits": 2, "sampled": false, "interval": 1, "unknown_points": 0, "unknown_reasons": [], "renames": 0, "no_comparison": "the only baseline available is the commit that CREATED this file…", "notices": ["…"], "gaps": []}

GET /api/v1/hotspot-trend

Are the hotspots coming down?

MCP tool: hotspot_trend. Authentication: API key or OAuth access token

The churn-weighted MEAN complexity density of a hotspot set, over time, for one repository, a group, a label or every repository you can see. Built entirely from complexity_trend's points — each file measured at every commit that touched it — with no second walk and no second analysis pass.

IT IS A MEAN AND NEVER A SUM. A summed complexity trend rises when a team ships and falls when it is laid off, whatever the code is like; it measures headcount. A mean is scale-free, which is the one property this has to have before the question can be asked at all. The weight is CHURN rather than size, because a size-weighted mean makes the biggest file the metric and the biggest files are usually generated.

THE SET IS FIXED AT EACH REPOSITORY'S NEWEST COMMIT and is stated as such: the question is "did the files we are worried about TODAY improve". A file that improved its way out of the ranking still contributes; one that rotted its way in since the baseline is not here.

THE SPAN IS NARROWED to the interval where every file in the set exists. Measured on a real repository, an aggregate over a set whose membership changed read 267.4 at three files and 235.6 at eleven a month later, arrowed DOWN, and nothing had improved — the denominator moved. So the span starts where the last member does, the narrowing is reported, and every point carries the file count it was taken over.

TESTS AND BUILD FILES ARE OUT by default and file_set says so on every response. A table-driven test gets longer and denser as coverage improves, so a headline driven by a test suite moves the wrong way when the right thing is happening.

REQUIRES HISTORY, and requires the analysis pass to have ranked the repository. Repositories that supply neither are reported as gaps rather than left out.

Parameters:

Name In Type Required Default Maximum Description
repo query string no Narrow to one or more repositories, comma-separated. Repository identifier, clone URL, owner/name, or a unique name suffix. Omit it for every repository the calling principal may read the history of.
group query string no Narrow to one repository group — the unit authorisation is granted over, and the ownership filter, so "everything the payments team owns" is one call. Applied inside the authorised set, never instead of it.
label query string no Narrow to one repository label, which is the looser categorisation beside a group. Applied inside the authorised set.
days query integer no 90 The churn window that both RANKS the set and WEIGHTS the headline, in days. 0 disables churn: the ranking becomes complexity alone and no file has a weight, so the headline falls back to the unweighted mean and the response says it did.
limit query integer no 12 How many of the ranked files the set holds, clamped at 25. This is the bound an unbounded scope is answered with: every file at every commit is a walk that does not terminate on a real estate, so the answer is the top N hotspots across the scope with the candidate count and the ceiling stated. There is no offset because a second page of a mean is a DIFFERENT MEAN over a different set, not the rest of this one — the rest of the ranking is hotspots, and one file at full resolution is complexity_trend.
file_set query string no production Which files may enter the set. production, the default, leaves test and build files out; all measures everything the analysis pass kept. Stated on every response either way, because an exclusion nobody can see is an accident of the walker rather than a decision. One of: production | all.
since query string no Bound every member's walk to commits at or after this instant — an RFC 3339 timestamp or a bare YYYY-MM-DD date. Omit for the whole history; the span is narrowed to where every member exists in either case.
boundary query string no Split the same series in two and compare the sides — a date such as 2026-03-01 for "we started doing it this way in March", or the word agent to split on commits carrying a Co-authored-by trailer that names a coding agent. Each side reports the MEAN density movement per commit, never a total, and the agent split's two sides are "attributable to an agent" and "everything else" — never "human-written", because inline completion leaves no trace and a squash drops the trailers. The attributable share travels beside the comparison.

Response fields:

Field Type Description
measures string What the numbers are OF: always file. Every density here is a file's, averaged over files, never a function's.
scope object Which repositories this is over and how they were selected.
  kind string estate, repository, group or label.
  value string The selector, empty for the whole visible estate.
  repositories integer How many repositories are in scope. EXACT, and the number to scale by: names below is a capped sample.
  names array of string Their identifiers, capped at 20. A repository, group or label selection under the cap is listed in full; only an estate-wide call samples. Enumerating them cost 64KB on a 448-repository estate and grew linearly.
as_of object The commit each repository's hotspot set was ranked at. On every response because the set is FIXED there rather than recomputed per point, and a reader has to know which commit to know which question was answered.
  repositories integer How many repositories were ranked. EXACT.
  unavailable integer How many of them had NO commit to rank at, so they contributed nothing. EXACT, and the half that had to survive summarising: a scope where sixty repositories had no commit is a different answer from one where every repository did, and a sample cannot say that.
  commits array The commit per repository, capped at 20.
    repository string The repository identifier.
    commit_sha string The commit its inventory was taken at.
unit string What the numbers are: cyclomatic complexity as scc counts it, over code lines, per 1,000. Calibrated against nothing, so the trend is the answer and an absolute value is not a grade.
method string How the number was arrived at, in one sentence, because a mean over a set is only readable beside the rule that built the set.
window_days integer The churn window that ranked and weighted.
file_set string production or all — which files were eligible.
set object The hotspot set this is over, and what bounded it.
  files integer How many files contributed at least one measurement.
  distinct_paths integer How many of the members above are distinct PATHS rather than distinct (repository, path) pairs. Lower than files means the same path appears under more than one repository, which forks, mirrors and in-flight migrations produce: the ranking is that much narrower than it looks and the duplicates crowded out genuine entries. They are NOT merged, because two copies diverge the moment either is edited and choosing a canonical one is a judgement with no basis.
  candidates integer How many files ranked at all across the scope, before limit cut the set. This is what says whether the ceiling bound the answer.
  limit integer The set size applied.
  ceiling integer The hard maximum limit is clamped to.
  declined boolean limit was above the ceiling and was clamped.
  dropped array Ranked files that produced no measurable point, with the reason. EXCLUDED from every mean rather than counted as zero, and listed so a set of twelve reported over nine is visible.
    repository string Which repository.
    path string Which file.
    why string Why nothing could be measured.
span object The interval reported over, and what narrowed it. The span starts where the LAST member of the set was first measured, so the member count is constant across every point — an average over a set whose membership changes falls when files join it, and that fall is not about the code.
  start string RFC 3339 instant the series begins at.
  end string RFC 3339 instant of the newest measurement.
  files integer The constant member count the span holds.
  narrowed boolean start is later than the oldest member's first measurement.
  narrowed_by string repository:path of the member that decided start.
  oldest_member_starts string Where the series would have begun without the guard.
  dropped_days integer How much history the narrowing gave up.
  days integer How long the series ACTUALLY covers. Read this and not window_days, which is the churn window that ranked the set and is a different number: the two sat side by side with nothing saying so, and a verdict computed over 8 days was read as covering 90.
  policy string How a member younger than the window was handled. trim-set dropped it and kept the survivors' history, which is what happens whenever a churn window is in force. intersect clipped the span to it, which is what days=0 produces and what trimming falls back to when every member is younger than the window, because an aggregate over no files answers nothing. Both are defensible answers to a real question, so the response says which one it gave.
  trimmed_members integer How many members were dropped for being born inside the window. They are in set.dropped with the reason, never silently removed.
hotspot_complexity object THE HEADLINE: the churn-weighted mean density over the set, at each reporting instant.
  scope string What population this number is over, in prose.
  weighting string churn, or the reason it is not.
  points array The series, oldest first.
    when string RFC 3339 reporting instant.
    value number The mean density at that instant.
    files integer How many files this point is a mean over. CONSTANT across the series by construction, and published on every point so a moving denominator cannot be read as a moving metric.
    changed integer How many members had a new measurement since the previous instant, so a flat stretch reads as "nothing was touched".
  from number The first point's value.
  to number The last point's value.
  change_pct number Percentage movement from first to last.
  verdict string improving, worsening or stable, keyed on DENSITY and never on raw complexity.
  available boolean False when there was nothing to compute.
  unavailable string Why, when available is false. An absent number is not a zero.
mean_complexity object THE CONTROL: the same series unweighted, over the same files. Where it disagrees with the headline, the difference is the churn weighting rather than the code. Same shape as hotspot_complexity.
worst_file object The single densest file in the set today, named, with its own movement — the one a reader can act on this afternoon and the one a mean hides. Null when no member could be measured.
corpus_today object The unweighted mean density over EVERY analysed file in scope, at the newest commit. A point in time and not a series, deliberately: a series over every file is one read per file per commit over the estate. It is what says whether the set is representative of the corpus it came from.
  scope string The population, stated, because it is not the set above.
  files integer How many analysed files it is over.
  density number The mean density.
  files_left_out integer Analysed files the file_set excluded.
members array One row per file in the set, so the mean can be read back to the files that made it.
  repository string Which repository.
  path string Which file, as it is today.
  changes integer Its churn in the window, which is its weight.
  points integer How many measurements its series carries.
  from_density number Density at the baseline point.
  to_density number Density at the newest measured point.
  complexity integer Complexity at that newest point.
  code integer Code lines at that newest point, which is the denominator.
  verdict string improving, worsening, stable or unknown. Keyed on DENSITY: a file that shrank while its complexity fell can still be worsening, and a rule keyed on raw complexity is blind to it. unknown is never stable.
  why string Why the verdict is unknown. Absent otherwise.
  basis string The rule that chose this file's baseline pair.
boundary object The before-and-after comparison, present only when boundary was given. Each side carries the MEAN density movement per commit and never a total.
  kind string date or agent.
  value string The date, or what the agent split was read from.
  available boolean False when one side has nothing in it.
  unavailable string Why there is no comparison. A side of zero commits reported as a mean of 0.0 reads as "that made no difference", which is the wrong answer rather than the missing one.
  attributable_commits integer How many measured commits carry a Co-authored-by trailer naming a coding agent. The remainder carry no such trailer, and that is the ONLY way to be unattributable here: a transition is counted at all only after the creating commit and both density reads have been excluded. So the gap is mostly ordinary work rather than a measurement failure, and it also holds any agent work whose trace was lost, which is why the other side is "everything else" and never "human-written".
  measured_commits integer How many measured transitions the whole set produced. This counts the EVIDENCE and not the comparison: it is every transition over every member, including members the comparison excluded, because a reader asking how much was seen means all of it.
  excluded_files integer How many members were left out of the comparison for having changed on ONE side of the boundary only. EXACT; excluded below is a capped sample. A file that did not exist before the boundary cannot be compared across it, and counting it on one side puts the two means over different populations — the same error the span logic gives up history to avoid.
  excluded array of string repository:path of the excluded members, capped. Named rather than only counted, because "two of these twelve files did not exist before June" is itself an answer to the question being asked.
  sides array Exactly two, in order.
    label string The side's name. For an agent split these are exactly "attributable to an agent" and "everything else".
    commits integer Commits on this side.
    files integer Files this side touched. EQUAL on both sides by construction: the comparison runs only over members changed on both, so a difference here would mean the two means were over different populations.
    mean_density_change number Mean density movement per commit, in complexity per 1,000 code lines. Negative is the code getting easier per line.
    commits_that_raised_density integer How many made it denser.
    commits_that_lowered_density integer How many made it less dense.
contributors object How the work on this file is SPREAD, and it names nobody. The subject is the file: "this file improved across nine contributors" and "across one" are different facts about the code and the risk it carries. ABSENT when no commit touching the file could be attributed, rather than an object of zeroes, because "0 contributors" reads as a file nobody works on instead of one nobody could attribute.
  commits integer Attributable commits the answer is over. Always present: it is the coverage of the evidence rather than a property of the people.
  distinct integer How many different people. Present only ABOVE analytics.concentrationFloor; below it you get band instead, because a count identifies when the denominator is small.
  top_share number The largest contributor's share of those commits, 0 to 1. Present only above the floor, for the same reason as distinct: "one person made 100%" names them in a team of four as well as a name would.
  band string "few" when the distinct count is below analytics.concentrationFloor. A band and never silence: an absent answer is indistinguishable from an unmeasured one, and silence would read as nothing to worry about.
  names array WHO they were, most commits first. ABSENT ON EVERY DEFAULT INSTALL: it appears only when the operator has set analytics.attributeAuthors, which also makes per_user_breakdown read true on /api/v1/usage. The concentration floor still applies, so a file below it names nobody even with attribution on — turning it on widens what you can see and does not switch off the anonymity floor underneath it.
    name string The commit author's identity, as git recorded it.
    commits integer How many of this file's attributable commits were theirs.
notices array of string Sentences a consumer must show the reader — the unit and method, the fixed set, the narrowed span, the file set, the headline and its control. Prose rather than flags, because a flag a client does not render reaches nobody.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: Are the payments team's hotspots coming down?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/hotspot-trend?group=payments&days=90&limit=12"
{"scope": {"kind": "group", "value": "payments", "repositories": 3, "names": ["example__shop"]}, "as_of": {"repositories": 3, "unavailable": 0, "commits": [{"repository": "example__shop", "commit_sha": "9b1c…"}]}, "unit": "cyclomatic complexity counted by scc; density is complexity per 1,000 code lines", "window_days": 90, "file_set": "production", "set": {"files": 11, "candidates": 143, "limit": 12, "ceiling": 25, "declined": false, "dropped": []}, "span": {"start": "2025-08-04T00:00:00Z", "end": "2026-09-17T00:00:00Z", "files": 11, "narrowed": true, "narrowed_by": "example__shop:orders.go", "dropped_days": 412, "days": 409, "policy": "trim-set", "trimmed_members": 1}, "hotspot_complexity": {"scope": "the hotspot set measured here, weighted by each file's change count in the churn window", "weighting": "churn", "points": [{"when": "2025-08-04T00:00:00Z", "value": 267.4, "files": 11, "changed": 0}, {"when": "2026-09-17T00:00:00Z", "value": 231.2, "files": 11, "changed": 4}], "from": 267.4, "to": 231.2, "change_pct": -13.54, "verdict": "improving", "available": true}, "worst_file": {"repository": "example__shop", "path": "orders.go", "verdict": "worsening", "to_density": 150.2}, "corpus_today": {"files": 1423, "density": 41.2, "files_left_out": 318}, "members": [], "notices": ["…"], "gaps": []}

Which repositories are improving, side by side

Not an MCP tool: a side-by-side page for people; an agent asking whether code is improving has hotspot_trend, which answers for any scope with every caveat attached Authentication: API key or OAuth access token

One row per repository you may read the history of, saying whether its complexity is improving, worsening or stable over the last 30, 90, 180 or 365 days, with the series a chart draws it from.

EVERY NUMBER IS hotspot_trend's: a row is that endpoint's churn-weighted mean complexity density for one repository, read across the window. No second metric, and improving means here exactly what it means there.

COMPUTED IN THE BACKGROUND, never on this request. Each repository is rebuilt after its history is refreshed, so this call is a cache read, and a repository not reached yet is pending and counted as pending. insufficient is a repository with no measurable movement in the window — no history, nothing committed in it, too few readings — with the reason. Neither is ever reported as stable.

THE CHART IS DRAWN ON CHANGE, NOT LEVEL. Densities differ by an order of magnitude between repositories for reasons that are not quality, so each series is the change from that repository's own first reading in the window. The rows carry both.

Filters narrow inside what you may see, never beside it: a repository you cannot see contributes no row, no count and no place in a ranking.

Parameters:

Name In Type Required Default Maximum Description
window query integer no 90 The window in days, ending now. One of four presets, because each is computed ahead of time; any other window would have to be computed on the request, which takes minutes over a large estate. One of: 30 | 90 | 180 | 365.
repo query string no Only these repositories, comma-separated. Repository identifier, clone URL, owner/name, or a unique name suffix. This is "the ones I care about": pick them, bookmark the URL, and that is the view.
group query string no Only repositories in this repository group. Applied inside the authorised set, never instead of it.
label query string no Only repositories carrying this label. Applied inside the authorised set.
name_like query string no Only repositories whose name or identifier CONTAINS this, case-insensitively: a substring, not a glob or a regex, as list_repositories spells it.
verdict query string no Only rows with this verdict. counts on the response is taken before this filter, so it still says how many of each there are. One of: improving | worsening | stable | insufficient | pending.
sort query string no worsening Row order. worsening, the default, puts the largest rise first; improving the largest fall; density the highest current density; churn the most changes in the window; name alphabetical. Rows with no movement sort after every row with one, except by name. One of: worsening | improving | density | churn | name.
offset query integer no 0 How many repositories to skip before listing. With max_results it is a page: offset 0 is the first page, offset 50 with max_results 50 is the second. max_results is a hard maximum rather than a default, so this is the only way to reach what lies past it. It is an offset and not a cursor: each request is a fresh read, so a row added or removed between two pages may be seen twice or missed once. An offset past the end is an empty list and not an error, because total is the only way to know where the end was and it may move. It does not change total, which counts the whole set before either parameter is applied.
max_results query integer no 50 200 How many repositories one page returns, at most 200. Each row carries its series, so a page is bounded by its own size and never by the estate's.

Response fields:

Field Type Description
window_days integer The window this answer is over, in days, ending now.
windows array of integer Every window that is computed and may be asked for.
threshold_pct number How far the density has to move, in per cent, to count as improving or worsening rather than stable. The same threshold hotspot_trend uses.
unit string What the densities are: cyclomatic complexity as scc counts it, per 1,000 code lines.
method string How each repository's line is computed.
scope object Which repositories the filters selected, before verdict.
  kind string estate, repository, group or label.
  value string The selector, empty for everything visible.
  repositories integer How many repositories the filters selected.
counts object How many of the selected repositories carry each verdict: improving, worsening, stable, insufficient and pending. Taken before the verdict filter.
computed object When the rows were computed.
  oldest string The oldest computation among the selected rows, RFC 3339. Empty when none is computed.
  newest string The newest computation, RFC 3339.
  pending integer How many selected repositories are not computed yet.
sort string The order the rows are in.
total integer How many rows match, before paging.
offset integer The offset this page was read at, echoed so a paging caller can check what it got rather than what it asked for.
truncated boolean Whether rows exist past this page. Read on with offset.
repositories array One row per repository on this page.
  repository string The repository identifier.
  name string Its human-facing name.
  verdict string improving, worsening, stable, insufficient or pending.
  why string Why there is no movement, for insufficient and pending. Empty otherwise.
  start_density number The first reading in the window. Zero when there is no movement.
  end_density number The newest reading.
  change_pct number The change from start_density to end_density, in per cent.
  churn integer Changes to the measured files in the window, the weight the mean was taken with.
  files integer How many files the hotspot set measured.
  commit_sha string The commit the row was computed at.
  computed_at string When the row was computed, RFC 3339. Empty when pending.
  span_start string The first reading's instant.
  span_end string The newest reading's instant.
  points array The series, oldest first, as change from the first reading.
    when string The reading's instant, RFC 3339.
    change_pct number Per cent change from the first reading.
chart object The lines a chart should draw: never more than ten, chosen from every matching row rather than from this page.
  selection string all when ten or fewer rows are measured and every one is drawn; extremes when the five most improved and five most worsened are.
  measured integer How many matching rows have a movement to draw.
  repositories array The chosen rows, in the same shape as repositories.
    repository string The repository identifier.
    name string Its human-facing name.
    verdict string improving, worsening, stable, insufficient or pending.
    why string Why there is no movement, for insufficient and pending. Empty otherwise.
    start_density number The first reading in the window. Zero when there is no movement.
    end_density number The newest reading.
    change_pct number The change from start_density to end_density, in per cent.
    churn integer Changes to the measured files in the window, the weight the mean was taken with.
    files integer How many files the hotspot set measured.
    commit_sha string The commit the row was computed at.
    computed_at string When the row was computed, RFC 3339. Empty when pending.
    span_start string The first reading's instant.
    span_end string The newest reading's instant.
    points array The series, oldest first, as change from the first reading.
      when string The reading's instant, RFC 3339.
      change_pct number Per cent change from the first reading.
notices array of string Sentences a reader must see: what improving means, how many rows are pending or insufficient, and which lines the chart chose.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: Which of the payments team's repositories are getting worse?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/repository-trends?group=payments&window=90"
{"window_days": 90, "windows": [30, 90, 180, 365], "threshold_pct": 5, "unit": "…", "method": "…", "scope": {"kind": "group", "value": "payments", "repositories": 3}, "counts": {"improving": 1, "worsening": 1, "stable": 0, "insufficient": 1, "pending": 0}, "computed": {"oldest": "2026-10-12T09:00:00Z", "newest": "2026-10-12T09:04:00Z", "pending": 0}, "sort": "worsening", "total": 3, "offset": 0, "truncated": false, "repositories": [{"repository": "example__shop", "name": "example/shop", "verdict": "worsening", "why": "", "start_density": 182.4, "end_density": 201.9, "change_pct": 10.69, "churn": 41, "files": 25, "commit_sha": "9b1c…", "computed_at": "2026-10-12T09:04:00Z", "span_start": "2026-07-14T09:00:00Z", "span_end": "2026-10-11T16:20:00Z", "points": [{"when": "2026-07-14T09:00:00Z", "change_pct": 0}, {"when": "2026-10-11T16:20:00Z", "change_pct": 10.69}]}], "chart": {"selection": "all", "measured": 2, "repositories": []}, "notices": ["…"], "gaps": []}

GET /api/v1/risk

Which files carry the most risk, factor by factor

MCP tool: risk_ranking. Authentication: API key or OAuth access token

The refactor list: every analysed file ranked by four factors reported SIDE BY SIDE — complexity, churn over the instance's window, the number of DISTINCT AUTHORS who changed it in that window, and findings in production code (test-file findings are counted beside them, never in them). For one repository, several, or every repository you may read the history of.

NOT A PRODUCT OF THE FACTORS. A product zeroes when any factor is zero, ranks whichever detector is noisiest, and has to turn an unknown into a number. Each factor is instead placed at a PERCENTILE within its own repository, the row's score is the mean of its known percentiles, and every input is on the row so the reader can see which one is carrying it. elevated names the factors a file is in its repository's top 10% for.

UNKNOWN IS NEVER ZERO. A source with no history, a shallow clone whose history does not cover the window, a walk cut short, or a deep pass that has not run leaves a factor null and listed in unknown, with a gap saying why — and it is left out of the mean rather than ranking the file low.

FILE SCOPE. Every number is about a whole file and none is attributed to a function in it. Authors are a COUNT and never names; a count below the instance's anonymity floor is reported as the band few.

The same factors reach read_file as a one-sentence advisory when a file is in its repository's top 10% for any of them. Nothing here is computed on the request: the table is built after each index from the commit walk hotspots already uses.

Parameters:

Name In Type Required Default Maximum Description
repo query string no Narrow to one or more repositories, comma-separated. Repository identifier, clone URL, owner/name, or a unique name suffix. Omit it for every repository the calling principal may read the history of.
offset query integer no 0 How many files to skip before listing. With max_results it is a page: offset 0 is the first page, offset 50 with max_results 50 is the second. max_results is a hard maximum rather than a default, so this is the only way to reach what lies past it. It is an offset and not a cursor: each request is a fresh read, so a row added or removed between two pages may be seen twice or missed once. An offset past the end is an empty list and not an error, because total is the only way to know where the end was and it may move. It does not change total, which counts the whole set before either parameter is applied.
max_results query integer no 20 1000 Cap on rows returned in one page. files_total is counted before it. A default a caller may raise, up to a hard maximum of 1000; page past that with offset.

Response fields:

Field Type Description
scope string What every number was computed at: always file. No figure here is about a function or a class, and none may be presented as one.
window_days integer The churn and author window, in days, measured back from when each repository's table was built after its last index.
threshold_percent integer The share of a repository's files a factor must place a file in, from the top, to be listed in elevated.
files array Ranked files, highest mean percentile first. This page only.
  repository string The repository identifier.
  path string Path relative to the repository root.
  language string The language scc detected.
  commit_sha string The commit the analysis described, or "unavailable" for a source with no commits.
  scope string Always file: every figure is about the whole file, never a symbol in it.
  complexity integer The file's cyclomatic complexity, from scc.
  churn integer Commits touching the file inside the window. Null when UNKNOWN, never zero.
  authors integer DISTINCT authors of those commits — a count, never a name. Null when UNKNOWN, and null beside authors_band when the count is below the anonymity floor.
  authors_band string few when the distinct count is below the instance's anonymity floor; absent otherwise.
  findings integer Findings in production code. Null when UNKNOWN, never zero.
  test_findings integer Findings in test files, reported beside and never counted in findings.
  percentiles object Each KNOWN factor's percentile within its repository, 0 to 100: the share of files with a strictly lower value. An unknown factor has no entry.
  score number The mean of the known percentiles. The order, not a measurement.
  elevated array of string Factors this file is in its repository's top 10% for.
  unknown array of string Factors that could not be measured. Never read an unknown factor as low.
files_total integer Files ranked across the scope, counted before offset and max_results.
offset integer The offset this page was read at, echoed so a paging caller can check what it got rather than what it asked for.
truncated boolean Rows remain after this page: files_total is greater than offset plus the rows returned.
notices array of string Sentences a consumer must show the reader: what the order is and that an unknown factor is not zero.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: What should we refactor first?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/risk?repo=shop&max_results=1"
{"scope": "file", "window_days": 90, "threshold_percent": 10, "files": [{"repository": "example__shop", "commit_sha": "9b1c…", "path": "orders.go", "language": "Go", "scope": "file", "complexity": 212, "churn": 83, "authors": 4, "findings": 2, "test_findings": 5, "percentiles": {"complexity": 97, "churn": 99, "authors": 95, "findings": 88}, "score": 94.75, "elevated": ["complexity", "churn", "authors"], "unknown": []}], "files_total": 143, "offset": 0, "truncated": true, "notices": ["FILE SCOPE: …"], "gaps": []}

GET /api/v1/repos/{repo}/coupling

The files that usually change with this one

MCP tool: change_coupling. Authentication: API key or OAuth access token

What else you will have to touch. Files that changed in the same commits as the one you named, with SUPPORT (how many commits contained both) and CONFIDENCE (of the commits touching yours, what fraction touched the other).

READ BOTH NUMBERS. Confidence 1.0 over two commits and confidence 0.4 over two hundred are not the same claim, and a support floor is applied by default for exactly that reason.

Commits touching more than the configured file count — reformats, licence sweeps, generated-code refreshes — are EXCLUDED, because one such commit couples every file to every other. The count that was excluded is in the response.

REQUIRES HISTORY. A directory source has no commits and a shallow clone has few. Both answer with a populated gaps array rather than an empty list: in a migration an empty coupling result reads as "safe to change", and that is the false conclusion this endpoint must never hand anybody.

Parameters:

Name In Type Required Default Maximum Description
repo path string yes Repository identifier, clone URL, owner/name, or a unique name suffix.
path query string yes The file to report the blast radius of, relative to the repository root.
min_support query integer no 2 Minimum commits a pair must share. 1 admits coincidences — most obviously the initial import, which touches everything a repository started with.
min_confidence query number no 0.3 Minimum fraction of this file's commits that also touched the other, between 0 and 1.
limit query integer no 20 1000 How many coupled files to return.

Response fields:

Field Type Description
repository string The repository identifier this result came from.
commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
path string The file that was asked about.
commits integer Commits touching that file, after bulk commits were excluded. It is the denominator of every confidence below.
coupled array Files that change with it, strongest first.
  path string The coupled file.
  support integer Commits that contained both files.
  confidence number support divided by commits — the fraction of this file's changes that also changed that one.
coupled_total integer How many files passed both thresholds, before limit trimmed the list. Raising limit returns more of these.
excluded_commits integer Commits skipped for touching more than bulk_commit_threshold files.
bulk_commit_threshold integer The file count above which a commit was excluded (analysis.history.bulkCommitFiles).
min_support integer The support floor that was applied.
min_confidence number The confidence floor that was applied.
commits_scanned integer Commits the cached walk covered, before exclusions.
truncated boolean The LIST was cut short by limit: coupled_total files passed the thresholds and fewer were returned. Raise limit for more.
commits_truncated boolean The WALK was cut short: the commit cap was reached, so older commits are not represented in any support or confidence here. No request parameter widens this — it is analysis.history.maxCommits, which the operator sets.
notices array of string Sentences a consumer must show the reader — how many commits were excluded, truncation, a partial walk.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: What else will I have to touch?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/repos/shop/coupling?path=orders.go"
{"repository": "example__shop", "commit_sha": "9b1c…", "path": "orders.go", "commits": 4, "coupled": [{"path": "invoice.go", "support": 4, "confidence": 1}], "coupled_total": 1, "excluded_commits": 2, "bulk_commit_threshold": 50, "min_support": 2, "min_confidence": 0.3, "commits_scanned": 66, "truncated": false, "commits_truncated": false, "notices": ["2 commits touching more than 50 files were excluded; a reformat commit couples every file to every other."], "gaps": []}

GET /api/v1/repos/{repo}/dependencies

What a file imports, what imports it, and which tests cover it

MCP tool: file_dependencies. Authentication: API key or OAuth access token

One file's dependency graph, read from the analysis pass: what it imports, what imports it, one or two hops out, and — with tests=only — the runnable tests that cover it. The three questions an agent asks around an edit: what to read first, how far the change reaches, and which tests to run.

FILES AND PACKAGES, NEVER SYMBOLS. An edge says one file imports another, or imports a package — a Go package, a Python namespace package — which is a directory and not any one file in it. There is no calls edge and there will not be one: which function is used is answered by reading the two files.

Every row carries a KIND — file, package, same_package (Go files of one package, which see each other without importing and are how a Go file's tests are found), name_convention (foo_test.go, test_foo.py, foo.test.ts) or mock (jest.mock, vi.mock) — and a RESOLUTION: exact when the language's own rules name the target, probable when a convention mapped it (a tsconfig alias, a Python project root), external when it is outside the repository. A hop-2 row is only as sure as the weaker of its two links.

AN UNRESOLVED IMPORT IS NOT A MISSING ONE. Imports that named nothing in this repository — a computed require(), a relative path to a deleted file — are counted and named in unresolved, never dropped. A file in a language this release reads no imports from (TypeScript, JavaScript, Python and Go are read), one the analysis pass skipped, and one whose parse may have lost imports each answer with a gap rather than an empty list.

DEPTH STOPS AT 2. Past two hops a typical file reaches half of a test suite, so further hops are COUNTED in further and never listed. Nothing is parsed on the request: the graph was resolved when the repository was last analysed.

Parameters:

Name In Type Required Default Maximum Description
repo path string yes Repository identifier, clone URL, owner/name, or a unique name suffix.
path query string yes The file to report on, relative to the repository root.
direction query string no both imports for what the file depends on, imported_by for what depends on it — the blast radius of a change — or both. One of: imports | imported_by | both.
depth query integer no 1 Hops to list: 1 or 2. A larger value is reduced to 2 and says so; what lies further is counted in further and never returned as rows.
tests query string no include only for the runnable tests among the rows — the tests to run after editing this file; exclude for production files only; include for both. Test SUPPORT (fixtures, mocks, conftest.py) is never a runnable test. One of: exclude | include | only.
resolution query string no external The weakest resolution to return: exact for edges the language's own rules decided, probable to add convention-mapped edges, external (the default) for everything. One of: exact | probable | external.
offset query integer no 0 How many rows to skip before listing. With max_results it is a page: offset 0 is the first page, offset 50 with max_results 50 is the second. max_results is a hard maximum rather than a default, so this is the only way to reach what lies past it. It is an offset and not a cursor: each request is a fresh read, so a row added or removed between two pages may be seen twice or missed once. An offset past the end is an empty list and not an error, because total is the only way to know where the end was and it may move. It does not change total, which counts the whole set before either parameter is applied.
max_results query integer no 100 1000 Cap on rows returned in one page; total counts them all. A default a caller may raise, up to a hard maximum of 1000; page past that with offset.

Response fields:

Field Type Description
repository string The repository identifier this result came from.
commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
path string The file that was asked about.
file object What is known about the asked file itself.
  language string The language scc detected.
  state string read when its imports were read; no_import_query for a language this release reads no imports from; not_analysed when the analysis pass skipped it. Only read means its own imports are known.
  role string test (a runnable test), test_support or production.
  generated boolean The file carries a generated-code header.
direction string The direction answered.
depth integer The hops listed, after any reduction.
dependencies array The edges, hop 1 first, then path order. This page only.
  direction string imports or imported_by.
  hop integer 1 or 2.
  path string The file at the far end. Absent on a forwards package row and on an external row, which end at something that is not a file.
  package string On a package row, the package directory; on an external row, the import as written.
  members integer On a forwards package row, how many files the package holds.
  kind string file, package, same_package, name_convention or mock.
  resolution string exact, probable or external — on a hop-2 row, the weaker of its two links.
  role string test, test_support or production.
  generated boolean The file is generated code; edges into it swamp a blast radius.
  import string The import that produced the edge, as written. Absent on same_package and name_convention rows, which no statement produced.
  line integer The import's line in the importing file.
  via string On a hop-2 row, the hop-1 file it was reached through.
total integer Rows across every page, counted before offset and max_results.
offset integer The offset this page was read at, echoed so a paging caller can check what it got rather than what it asked for.
truncated boolean Rows remain past this page.
further object Per direction, how many more files are reachable past the last hop listed. Counts only: they are never rows.
unresolved array The asked file's imports that resolved to nothing in this repository.
  import string The import as written.
  line integer Its line.
  reason string Why it resolved to nothing.
  internal boolean It names this repository's own code (a relative path, the repository's module or package, an alias) rather than a dependency.
unresolved_total integer How many of the asked file's imports resolved to nothing.
notices array of string Sentences a consumer must show the reader: what package and probable rows mean, how runnable tests were identified, and what did not resolve.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: Which tests should I run after editing cart.go?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/repos/shop/dependencies?path=internal/cart/cart.go&direction=imported_by&tests=only"
{"repository": "example__shop", "commit_sha": "9b1c…", "path": "internal/cart/cart.go", "file": {"language": "Go", "state": "read", "role": "production", "generated": false}, "direction": "imported_by", "depth": 1, "dependencies": [{"direction": "imported_by", "hop": 1, "path": "internal/cart/cart_test.go", "kind": "name_convention", "resolution": "probable", "role": "test", "generated": false}, {"direction": "imported_by", "hop": 1, "path": "internal/cart/cart_test.go", "kind": "same_package", "resolution": "exact", "role": "test", "generated": false}], "total": 2, "offset": 0, "truncated": false, "further": {"imported_by": 3}, "unresolved": [], "unresolved_total": 0, "notices": ["Runnable tests are identified by each test runner's default file naming …"], "gaps": []}

GET /api/v1/repos/{repo}/definitions

Where a symbol is declared, within one repository

MCP tool: definitions. Authentication: API key or OAuth access token

Jump-to-definition, scoped to one repository. For the same question across every repository you may see, use GET /api/v1/definitions instead — the scope is the only difference and neither endpoint resolves anything.

PRECISE RESOLUTION IS NOT ATTEMPTED, here or anywhere in this product, and that is a decision rather than a gap in the implementation: a precise answer needs matching indexed package versions on both sides or an uploaded SCIP index, and that is where precision quietly dies. What you get is every declaration of the name, labelled.

Every result is labelled exact or probable and the two are never merged unmarked. A declaration a language grammar parsed at ingest is exact and carries a real kind; one the line-start heuristic guessed is probable. A language covered by neither produces a GAP saying so rather than an empty list: "we cannot resolve definitions for Cobol" and "this symbol is not defined here" are different answers and only one of them is safe to act on.

An empty answer says so IN THE RESPONSE, as a notice rather than as a gap: nothing failed, no declaration of the name was recognised in this repository, and it may well be declared in another one. That notice states the boundary rather than claiming to be complete across it — the SCOPE is exact and the COVERAGE is not, because a declaration is found by matching a parsed symbol table and that table holds the forms this build's grammars extract. Search for the name before concluding nothing declares it.

Parameters:

Name In Type Required Default Maximum Description
repo path string yes Repository identifier, clone URL, owner/name, or a unique name suffix.
symbol query string yes The identifier to resolve. Matched case-sensitively, and as a WHOLE identifier: Walk does not match ErrTerminateWalk or walkDirectory.
offset query integer no 0 How many locations to skip before listing. With max_results it is a page: offset 0 is the first page, offset 50 with max_results 50 is the second. max_results is a hard maximum rather than a default, so this is the only way to reach what lies past it. It is an offset and not a cursor: each request is a fresh read, so a row added or removed between two pages may be seen twice or missed once. An offset past the end is an empty list and not an error, because total is the only way to know where the end was and it may move. It does not change total, which counts the whole set before either parameter is applied.
max_results query integer no 50 1000 Cap on returned locations. A HARD MAXIMUM of 1000 rather than a default a caller raises: a larger value is clamped to it and the response says so in gaps. total counts every declaration before it bit, and offset reaches the ones past this page.

Response fields:

Field Type Description
offset integer The offset this page was read at, echoed so a paging caller can check what it got rather than what it asked for.
repository_name string The repository's own name — its normalised clone URL, or its path for a directory source. This is what to show a person; repository is the identifier to pass back.
locations array The declarations, in repository order.
  repository string The repository identifier this result came from.
  commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
  path string Path relative to the repository root.
  line integer 1-based line of the declaration.
  content string The declaring line.
  ranges array Where within content the symbol was found: [start, end) BYTE offsets, ascending, end exclusive. From the search that produced the line rather than from scanning it for the symbol, so a line naming both validate and valid marks the one that was matched. Empty is a real answer and means there is no offset to report — the line is still a location.
  language string The language scc detected.
  kind string What the declaration IS: function, method, constructor, class, interface, struct, enum, trait, type, module, property, macro, impl, constant or variable where a grammar read it, and the undifferentiated word "declaration" where the line-start heuristic classified it. It is the field to group, filter or disambiguate on when a name has several declarations: a class Router and a variable Router are not the same answer, and the declaring line only tells you which for a language you can read. Read it beside confidence rather than instead of it — confidence says how the answer was reached and kind says what was found.
  confidence string exact when a parser resolved it, probable when a heuristic did. Never absent: an unlabelled navigation result is one the caller cannot decide whether to trust.
total integer Locations found before max_results truncation.
references_total integer How many USES of the same name this repository holds — the other question, answered here so that a reader of a declaration does not have to ask a second time to find out whether anything calls it. ABSENT when this answer could not count them, and present as 0 when the name is genuinely used nowhere: those are different facts and only the second is safe to act on, so read it by presence and never by a non-zero test. It is omitted whenever the count would be a lower bound — a matching file in a language with no grammar and no declaration patterns, for one — because a lower bound presented as a count is the failure this product exists not to have.
notices array of string Sentences that change what this answer MEANS, above the data rather than beside it. Present and empty on an answer that needs none. An empty locations list carries one here, because a repository-scoped zero is otherwise indistinguishable from an estate-wide one: this endpoint resolves within one repository, so a symbol declared in another repository correctly returns nothing and that is a different fact from "nothing declares it". Not a gap — nothing was skipped and nothing was unanswerable. The same sentence also says what the answer does NOT know: the symbol table it was matched against covers the declaration forms this build's grammars extract, so an empty answer is "nothing here was recognised as declaring it" and never "nothing here declares it".
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: Where is RetryWithBackoff defined?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/repos/deploy-tool/definitions?symbol=RetryWithBackoff"
{"repository_name": "https://example.invalid/deploy-tool", "locations": [{"repository": "example__deploy-tool", "commit_sha": "4f0c...", "path": "retry.go", "line": 11, "content": "func RetryWithBackoff(ctx context.Context, attempts int) error {", "ranges": [[5, 21]], "language": "Go", "kind": "declaration", "confidence": "probable"}], "total": 1, "references_total": 1, "notices": [], "gaps": []}

GET /api/v1/repos/{repo}/references

Where a symbol is used, within one repository

MCP tool: references. Authentication: API key or OAuth access token

Find-references, scoped to one repository and labelled the same way definitions are. A match on a declaring line is a definition and is excluded here, so the two calls partition the matches rather than overlapping.

Precise resolution is not attempted; see definitions for why. EVERY answer here is repository-scoped, including a populated one: three uses means three uses IN THIS REPOSITORY, and estate-wide impact analysis is GET /api/v1/references. An empty answer restates the scope as a notice; a populated one does not, because a sentence on every response is one nobody reads.

A LOCATION HERE CARRIES NO kind, WHERE A DEFINITION DOES, and the asymmetry is structural rather than an omission: a reference is a name that appeared in a file, and nothing this endpoint does decides what the thing named is. Reporting a kind would be the resolution it says it does not perform, which is the same reason every result here is probable. To learn what a name declares, ask definitions.

Parameters:

Name In Type Required Default Maximum Description
repo path string yes Repository identifier, clone URL, owner/name, or a unique name suffix.
symbol query string yes The identifier to find uses of. Matched case-sensitively, and as a WHOLE identifier: Walk does not match ErrTerminateWalk or walkDirectory. Content search matches substrings; find-references does not.
offset query integer no 0 How many locations to skip before listing. With max_results it is a page: offset 0 is the first page, offset 50 with max_results 50 is the second. max_results is a hard maximum rather than a default, so this is the only way to reach what lies past it. It is an offset and not a cursor: each request is a fresh read, so a row added or removed between two pages may be seen twice or missed once. An offset past the end is an empty list and not an error, because total is the only way to know where the end was and it may move. It does not change total, which counts the whole set before either parameter is applied.
max_results query integer no 50 1000 Cap on returned locations. A HARD MAXIMUM of 1000 rather than a default a caller raises: a larger value is clamped to it and the response says so in gaps. total counts every use before it bit, and offset reaches the ones past this page.

Response fields:

Field Type Description
offset integer The offset this page was read at, echoed so a paging caller can check what it got rather than what it asked for.
repository_name string The repository's own name — its normalised clone URL, or its path for a directory source. This is what to show a person; repository is the identifier to pass back.
locations array The uses, in repository order.
  repository string The repository identifier this result came from.
  commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
  path string Path relative to the repository root.
  line integer 1-based line of the use.
  content string The using line.
  ranges array Where within content the symbol was found: [start, end) BYTE offsets, ascending, end exclusive. From the search that produced the line rather than from scanning it for the symbol, so a line naming both validate and valid marks the one that was matched. Empty is a real answer and means there is no offset to report — the line is still a location.
  language string The language scc detected.
  confidence string exact or probable, never absent.
total integer Locations found before max_results truncation.
definitions_total integer How many DECLARATIONS of the same name this repository holds — the other question, answered here so that a reader of a use list can see whether the thing being used is declared in the same repository at all. ABSENT when this answer could not count them, and present as 0 when the name is declared nowhere here: read it by presence and never by a non-zero test, because a repository whose deep pass has not run and a repository that declares the name nowhere are different facts. Omitted whenever the count would be a lower bound — the same rule references_total carries on the definitions endpoint.
notices array of string Sentences that change what this answer MEANS. Present and empty on an answer that needs none; an empty locations list carries one saying that uses were looked for in this repository only, because "nothing here uses it" and "nothing uses it" are different facts. A POPULATED answer carries none: every answer from this endpoint is repository-scoped, and a sentence on every response is one nobody reads.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: Who calls RetryWithBackoff?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/repos/deploy-tool/references?symbol=RetryWithBackoff"
{"repository_name": "https://example.invalid/deploy-tool", "locations": [{"repository": "example__deploy-tool", "commit_sha": "4f0c...", "path": "deploy.go", "line": 42, "content": "\tif err := RetryWithBackoff(ctx, 3); err != nil {", "ranges": [[11, 27]], "language": "Go", "confidence": "probable"}], "total": 1, "definitions_total": 1, "notices": [], "gaps": []}

GET /api/v1/definitions

Where a symbol is declared, across the whole visible estate

MCP tool: estate_definitions. Authentication: API key or OAuth access token

Jump-to-definition across every repository you may see, rather than one at a time. The scope is exactly what your permissions allow and is applied before anything is read, so a repository you cannot see contributes no result and no count.

WHAT exact MEANS HERE, AND WHAT IT DOES NOT. A declaration labelled exact was read by a language grammar during ingestion, so its name, kind, file and line are parsed facts and not a guess. That is a claim about WHAT EACH DECLARATION IS. It is NOT a claim that the right one was picked: no binding resolution is performed, no imports are followed and no types are inferred, so eleven declarations of Handler are eleven candidates and not one answer. Every response that carries exact locations says this in notices, and a caller that needs THE definition has to disambiguate by repository, path or signature.

A name matched in more files than the configured ceiling DECLINES rather than returning thousands of candidates: locations is empty, declined is true, and a gap names the candidate count and the ceiling. That is not a failure and not an absence — Error is a candidate in over five thousand files on a modest estate, and a name that common cannot be resolved by matching it. Ask one repository instead, or search the estate with filters.

Parameters:

Name In Type Required Default Maximum Description
symbol query string yes The identifier to resolve. Matched case-sensitively, and as a WHOLE identifier: Walk does not match ErrTerminateWalk or walkDirectory.
offset query integer no 0 How many locations to skip before listing. With max_results it is a page: offset 0 is the first page, offset 50 with max_results 50 is the second. max_results is a hard maximum rather than a default, so this is the only way to reach what lies past it. It is an offset and not a cursor: each request is a fresh read, so a row added or removed between two pages may be seen twice or missed once. An offset past the end is an empty list and not an error, because total is the only way to know where the end was and it may move. It does not change total, which counts the whole set before either parameter is applied.
path query string no The file you are asking from. Only files in its language, as scc names it from the file name, are matched, counted and held to the ceiling (.ts includes .d.ts). An extension scc cannot place filters nothing, and notices says so.
language query string no An scc language name (Go, TypeScript, Python; any case) for a caller with no file. WINS over path when both are given. Includes its own headers, typings or JSX. A name scc does not know filters nothing, and notices says so.
max_results query integer no 50 1000 Cap on returned locations. It caps the PAYLOAD, not the ceiling: total counts every location found before this bit. A HARD MAXIMUM of 1000 rather than a default a caller raises: a larger value is clamped to it and the response says so in gaps. offset reaches the rest.

Response fields:

Field Type Description
offset integer The offset this page was read at, echoed so a paging caller can check what it got rather than what it asked for.
locations array The declarations, best-ranked first.
  repository string The repository identifier this result came from.
  commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
  repository_name string The repository's own name, per location, because an estate-wide answer spans repositories and the identifier alone is not what to show a person.
  path string Path relative to the repository root.
  line integer 1-based line of the declaration.
  content string The declaring line.
  ranges array Where within content the symbol was found: [start, end) BYTE offsets, ascending, end exclusive. From the search that produced the line rather than from scanning it for the symbol, so a line naming both validate and valid marks the one that was matched. Empty is a real answer and means there is no offset to report — the line is still a location.
  language string The language scc detected.
  kind string What the declaration IS: function, method, constructor, class, interface, struct, enum, trait, type, module, property, macro, impl, constant or variable where a grammar read it, and the undifferentiated word "declaration" where the line-start heuristic classified it. It is the field to group, filter or disambiguate on when a name has several declarations: a class Router and a variable Router are not the same answer, and the declaring line only tells you which for a language you can read. Read it beside confidence rather than instead of it — confidence says how the answer was reached and kind says what was found.
  confidence string exact when a grammar parsed this declaration, probable when the line-start heuristic did. exact is about what the declaration IS and never about which declaration you meant — see notices.
total integer Locations found before max_results truncation.
truncated boolean More locations matched than max_results returned. False means total is the whole answer rather than a lower bound.
candidates integer How many FILES held a candidate match across the visible estate, before the ceiling and before max_results. This is the number the ceiling is compared against.
ceiling integer The candidate-file ceiling in force, so a decline can be acted on rather than only observed. Operator configuration (search.symbols.crossRepoCeiling); it is not a parameter of this call.
declined boolean True when candidates exceeded ceiling: nothing was classified and locations is empty. Read it before reading locations — a declined answer and "nothing declares this" are opposite facts and an empty list cannot tell them apart.
languages_searched array of string The languages this answer was filtered to, from path or language. EMPTY MEANS EVERY LANGUAGE WAS SEARCHED — no filter was asked for, or the one asked for could not be resolved. notices says which, in words.
notices array of string Sentences that change what this answer MEANS, above the data rather than beside it. Present and empty on an answer that needs none. An answer carrying exact locations always carries the sentence saying that exact means parsed and not resolved; a declined answer says it declined; an empty one says the estate declares the name nowhere it could see. A language-filtered answer says which languages it was filtered to and how to widen it; an unfiltered one with locations says how many languages they span and how to narrow it.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: Where is RetryWithBackoff declared anywhere?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/definitions?symbol=RetryWithBackoff"
{"locations": [{"repository": "example__deploy-tool", "repository_name": "https://example.invalid/deploy-tool", "commit_sha": "4f0c...", "path": "retry.go", "line": 11, "content": "func RetryWithBackoff(ctx context.Context, attempts int) error {", "ranges": [[5, 21]], "language": "Go", "kind": "function", "confidence": "exact"}], "total": 1, "truncated": false, "candidates": 3, "ceiling": 1200, "declined": false, "languages_searched": [], "notices": ["NOT FILTERED BY LANGUAGE: The name was matched in every language, and the 1 location found is Go. Pass path (the file you are asking from) or language to keep only that language: ...", "EXACT MEANS PARSED, NOT RESOLVED: ..."], "gaps": []}

GET /api/v1/references

Where a symbol is used, across the whole visible estate

MCP tool: estate_references. Authentication: API key or OAuth access token

Find-references across every repository you may see. A match on a declaring line is a definition and is excluded here, so the two calls partition the matches rather than overlapping.

EVERY RESULT IS probable AND THAT IS STRUCTURAL, not a limitation of the current version. A reference is a name that appeared in a file. Get in client.Get() and Get in type Get struct are the same three characters, and nothing here distinguishes them: no binding resolution, no import following, no type inference. There is no configuration and no future grammar that makes a reference exact, so treat every location as a candidate to check rather than as an established use.

THE SAME REASONING IS WHY NO LOCATION HERE CARRIES A kind, where every declaration from GET /api/v1/definitions does. A kind is a claim about what a name refers to, this endpoint resolves nothing, and a guessed kind on an unresolved name would be exactly the false precision the paragraph above rules out.

A name matched in more files than the configured ceiling DECLINES, exactly as definitions does and for the same reason.

Parameters:

Name In Type Required Default Maximum Description
symbol query string yes The identifier to find uses of. Matched case-sensitively, and as a WHOLE identifier: Walk does not match ErrTerminateWalk or walkDirectory. Content search matches substrings; find-references does not.
offset query integer no 0 How many locations to skip before listing. With max_results it is a page: offset 0 is the first page, offset 50 with max_results 50 is the second. max_results is a hard maximum rather than a default, so this is the only way to reach what lies past it. It is an offset and not a cursor: each request is a fresh read, so a row added or removed between two pages may be seen twice or missed once. An offset past the end is an empty list and not an error, because total is the only way to know where the end was and it may move. It does not change total, which counts the whole set before either parameter is applied.
path query string no The file you are asking from. Only files in its language, as scc names it from the file name, are matched, counted and held to the ceiling (.ts includes .d.ts). An extension scc cannot place filters nothing, and notices says so.
language query string no An scc language name (Go, TypeScript, Python; any case) for a caller with no file. WINS over path when both are given. Includes its own headers, typings or JSX. A name scc does not know filters nothing, and notices says so.
max_results query integer no 50 1000 Cap on returned locations. It caps the PAYLOAD, not the ceiling. A HARD MAXIMUM of 1000 rather than a default a caller raises: a larger value is clamped to it and the response says so in gaps. offset reaches the rest.

Response fields:

Field Type Description
offset integer The offset this page was read at, echoed so a paging caller can check what it got rather than what it asked for.
locations array The uses, best-ranked first.
  repository string The repository identifier this result came from.
  commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
  repository_name string The repository's own name, per location, because an estate-wide answer spans repositories.
  path string Path relative to the repository root.
  line integer 1-based line of the use.
  content string The using line.
  ranges array Where within content the symbol was found: [start, end) BYTE offsets, ascending, end exclusive. From the search that produced the line rather than from scanning it for the symbol, so a line naming both validate and valid marks the one that was matched. Empty is a real answer and means there is no offset to report — the line is still a location.
  language string The language scc detected.
  confidence string probable, always, on every result from this endpoint. A name match across an estate is a candidate and not a resolution.
total integer Locations found before max_results truncation.
truncated boolean More locations matched than max_results returned.
candidates integer How many FILES held a candidate match across the visible estate, before the ceiling and before max_results.
ceiling integer The candidate-file ceiling in force. Operator configuration (search.symbols.crossRepoCeiling); it is not a parameter of this call.
declined boolean True when candidates exceeded ceiling: nothing was classified and locations is empty. A declined answer and "nothing uses this" are opposite facts.
languages_searched array of string The languages this answer was filtered to, from path or language. EMPTY MEANS EVERY LANGUAGE WAS SEARCHED — no filter was asked for, or the one asked for could not be resolved. notices says which, in words.
notices array of string Sentences that change what this answer MEANS. Present and empty on an answer that needs none. A populated answer carries the sentence saying every location is a name match rather than a resolved use. A language-filtered answer says which languages it was filtered to and how to widen it; an unfiltered one with locations says how many languages they span and how to narrow it.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: Who uses RetryWithBackoff anywhere?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/references?symbol=RetryWithBackoff&path=cmd/deploy/main.go"
{"locations": [{"repository": "example__deploy-tool", "repository_name": "https://example.invalid/deploy-tool", "commit_sha": "4f0c...", "path": "deploy.go", "line": 42, "content": "\tif err := RetryWithBackoff(ctx, 3); err != nil {", "ranges": [[11, 27]], "language": "Go", "confidence": "probable"}], "total": 1, "truncated": false, "candidates": 3, "ceiling": 1200, "declined": false, "languages_searched": ["Go"], "notices": ["FILTERED BY LANGUAGE: only Go files were searched, because path names a Go file ...", "EVERY LOCATION IS A NAME MATCH: ..."], "gaps": []}

GET /api/v1/repos/{repo}/technologies

Which technologies one file uses, with the evidence

MCP tool: file_technologies. Authentication: API key or OAuth access token

The per-FILE half of the dependency index. find_usages answers "which files use boto3"; this answers "what does this file use", which is the question somebody reading the file has.

Evidence is the matched keyword and the line it was found on, so an attribution can be checked rather than trusted. Whole files are scanned, not just their first hundred lines, so a deep import is attributed.

Parameters:

Name In Type Required Default Maximum Description
repo path string yes Repository identifier, clone URL, owner/name, or a unique name suffix.
path query string yes File path relative to the repository root.

Response fields:

Field Type Description
technologies array One entry per attributed technology.
  repository string The repository identifier this result came from.
  commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
  technology string The technology that was matched.
  path string The file that was examined.
  language string The language scc detected.
  line integer 1-based line the evidence was found on.
  evidence string The matched keyword, so the attribution can be checked rather than trusted.
  confidence string exact or probable.
  version string The resolved version, absent when no manifest could answer. An absent version means unknown rather than none.
  version_source string lock, declared or inferred. A lock says what IS installed; a declared range says what MIGHT be, and only the first supports a vulnerability claim.
  version_confidence string exact when a lockfile pinned it and the import-to-package mapping is known, probable when either half was a convention. Separate from confidence above, which is about whether this file uses the technology at all.
  manifest_path string The manifest the version came from, relative to the repository root — "resolved from services/api/go.mod" can be opened and checked.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: What does this file use?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/repos/data-pipeline/technologies?path=app/deep_import.py"
{"technologies": [{"repository": "example__data-pipeline", "commit_sha": "7c9a...", "technology": "AWS SDK (Boto3)", "path": "app/deep_import.py", "language": "Python", "line": 154, "evidence": "boto3", "confidence": "probable"}], "gaps": []}

POST /api/v1/vulnerabilities

Which files use a vulnerable version of a dependency

MCP tool: find_vulnerable_usages. Authentication: API key or OAuth access token

The join nobody else can make from one index: the file that imports a package, the version the repository's manifests resolved it to, and the advisory that affects that version — answered from an advisory database that ships inside this binary and is NEVER fetched at runtime.

READ THE unknown ARRAY BEFORE CONCLUDING ANYTHING. An empty vulnerabilities array does NOT mean the estate is clean: a dependency whose version could not be resolved is reported as unknown, not as "not vulnerable", and telling somebody they are safe on the strength of an unresolved version is the failure this endpoint is shaped to prevent.

Two labels carry the uncertainty and both matter. match_confidence is exact only when a LOCKFILE pinned the version; a declared range says what MIGHT be installed and yields a probable match, which is a lead rather than a confirmation. advisory_database says how old the data is — an advisory published since then is not in this answer.

Each advisory carries the weakness classes its publisher recorded in cwe, in the same CWE-89 form a security finding from findings carries, so "the same weakness in our own code and in a dependency" is a comparison of two fields. It matches by exact identifier only. Only GitHub's advisories record CWEs; a GO, PYSEC or RUSTSEC advisory takes them from a record that shares one of its identifiers. An advisory with none reads cwe_state: unknown — the class was not recorded, which is not a claim that there is none — and notices says how many in prose.

This is estate context, not a replacement for Snyk or Dependabot. The differentiated claim is WHICH FILES, joined with ownership and findings; a dedicated scanner will always win on the breadth and freshness of the advisory data itself.

Request body:

Name In Type Required Default Maximum Description
technology body string no Only usages of this technology, as the detector names it — e.g. "Tokio".
package body string no Only this package, as the ECOSYSTEM names it — e.g. "tokio", "org.apache.logging.log4j:log4j-core". Different from technology, which is the detector's human-facing label.
ecosystem body string no Only dependencies in this ecosystem. Manifests are parsed for these six and no others. One of: python | go | rust | node | java | dotnet.
severity body string no Only advisories at this severity or worse. One of: critical | high | moderate | low | unknown.
repo body string no Restrict to one repository, by identifier or unique short name.
group body string no Restrict to one repository group — the ownership filter, so "everything the payments team owns" is one call. It can only narrow what the principal may already see.
include_unknown body boolean no true Return the dependencies whose version could not be resolved. Leaving it on is strongly advised: the count is reported either way, and turning it off is how a report comes to look cleaner than the estate is.
offset body integer no 0 How many entries to skip before listing. With max_results it is a page: offset 0 is the first page, offset 50 with max_results 50 is the second. max_results is a hard maximum rather than a default, so this is the only way to reach what lies past it. It is an offset and not a cursor: each request is a fresh read, so a row added or removed between two pages may be seen twice or missed once. An offset past the end is an empty list and not an error, because total is the only way to know where the end was and it may move. It does not change total, which counts the whole set before either parameter is applied.
max_results body integer no 50 1000 Cap on returned vulnerable usages, and on the unknown list beside them. The totals are reported before the cap. A HARD MAXIMUM of 1000 rather than a default a caller raises: a larger value is clamped to it and the response says so in gaps. offset pages both lists together.

Response fields:

Field Type Description
offset integer The offset this page was read at, echoed so a paging caller can check what it got rather than what it asked for.
vulnerabilities array One entry per file that uses a package at a version an advisory affects.
  repository string The repository identifier this result came from.
  commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
  path string Path relative to the repository root.
  language string The language scc detected.
  line integer 1-based line the import was found on.
  technology string The detector's name for it.
  ecosystem string One of the six.
  package string The package as the ecosystem names it.
  state string vulnerable, not_vulnerable or unknown. Never absent.
  match_confidence string exact when a lockfile pinned the version, probable when the version came from a declared range or a heuristic name mapping. A probable match is a lead, not a confirmation.
  advisories array The advisories affecting this version.
    id string The advisory identifier — a CVE, GHSA or RUSTSEC id.
    aliases array of string The same advisory's other identifiers.
    severity string critical, high, moderate, low or unknown.
    summary string One line describing the vulnerability.
    fixed_in string The first version that is not affected. ABSENT when no fix is published, which is the finding that most needs acting on.
    cwe_state string recorded or unknown, never absent. unknown means the advisory's publisher recorded no weakness class, or the bundle predates CWE data (advisory_database.cwe_recorded is false) — never that the vulnerability has no weakness.
    cwe array of string The weakness classes the advisory's publisher recorded, as CWE-89 — the same identifiers a security finding's cwe uses, so a weakness in the estate's own code and in a dependency can be matched, by exact identifier only. An advisory may record several. From OSV database_specific.cwe_ids, or from a record sharing one of this advisory's identifiers. Present only when cwe_state is recorded.
  version string The resolved version, absent when no manifest could answer. An absent version means unknown rather than none.
  version_source string lock, declared or inferred. A lock says what IS installed; a declared range says what MIGHT be, and only the first supports a vulnerability claim.
  version_confidence string exact when a lockfile pinned it and the import-to-package mapping is known, probable when either half was a convention. Separate from confidence above, which is about whether this file uses the technology at all.
  manifest_path string The manifest the version came from, relative to the repository root — "resolved from services/api/go.mod" can be opened and checked.
unknown array Dependencies whose vulnerability status could NOT be determined. This is not the same as not vulnerable and must never be reported as safe.
  repository string The repository identifier this result came from.
  commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
  path string Path relative to the repository root.
  line integer 1-based line the import was found on.
  technology string The detector's name for it.
  ecosystem string The ecosystem, where the language named one.
  state string Always "unknown".
  reason string Why the status could not be determined.
total integer Vulnerable usages found before max_results truncation.
unknown_total integer Dependencies whose status could not be determined, before truncation. Reported even when include_unknown is off.
by_severity object Vulnerable usages keyed by severity, counting only repositories this principal may see.
advisory_database object The database that answered: when it was built, how old it is, whether it can be relied on as current coverage, and where it came from. The sentence a reader must see is in notices rather than here, so it is stated once.
  loaded boolean False means no database is available at all.
  origin string "embedded" or the side-loaded bundle's path.
  signed boolean Whether the bundle's signature was verified.
  generated_at string When the bundle was built, RFC 3339.
  age_days integer How old it is, in whole days.
  advisories integer How many advisories it holds.
  stale boolean True when this database is not current coverage. A fixed definition an operator cannot change: no database at all, the compiled-in seed at any age, or a side-loaded bundle more than 30 days old. advisories.warnIfOlderThanDays governs the wording of the notice and never this field.
  stale_reason string Why, when stale: "absent", "seed" or "age". Absent when the database is current coverage.
  cwe_recorded boolean Whether the bundle carries CWE identifiers at all. False on a bundle built before they were kept, in which case every advisory's cwe_state is unknown.
notices array of string Sentences a consumer must show the reader — the staleness warning, the unknown-is-not-safe statement and, whenever any advisory is listed, what its cwe does and does not claim. They are prose rather than flags because a flag a client does not render reaches nobody.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: Everything the payments team owns that uses a vulnerable tokio

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  -X POST \
  -H 'Content-Type: application/json' \
  -d '{"group": "payments", "package": "tokio"}' \
  "$SEARCHCODE_URL/api/v1/vulnerabilities"
{
  "vulnerabilities": [
    {
      "repository": "example__rust-ledger",
      "commit_sha": "1a2b3c4d…",
      "path": "src/ledger.rs",
      "language": "Rust",
      "line": 3,
      "technology": "Tokio",
      "ecosystem": "rust",
      "package": "tokio",
      "state": "vulnerable",
      "match_confidence": "exact",
      "version": "1.20.0",
      "version_source": "lock",
      "version_confidence": "exact",
      "manifest_path": "Cargo.lock",
      "advisories": [
        {"id": "RUSTSEC-2023-0001", "aliases": ["CVE-2023-22466", "GHSA-7rrj-xr53-82p7"],
         "severity": "moderate",
         "summary": "reject_remote_clients is ignored on Windows named pipes", "fixed_in": "1.23.1",
         "cwe_state": "recorded", "cwe": ["CWE-665"]}
      ]
    }
  ],
  "unknown": [],
  "total": 1,
  "unknown_total": 0,
  "by_severity": {"moderate": 1},
  "advisory_database": {"loaded": true, "origin": "/etc/searchcode/advisories.bundle",
    "signed": true, "generated_at": "2026-08-06T00:00:00Z", "age_days": 3,
    "advisories": 41283, "stale": false, "cwe_recorded": true},
  "notices": ["About the advisory CWEs: an advisory's cwe is the weakness class its publisher recorded ..."],
  "gaps": []
}

GET /api/v1/dependency-licences

The licences of the estate's dependencies

MCP tool: licence_audit. Authentication: API key or OAuth access token

Every dependency the visible estate declares, with the licence its manifest recorded, rolled up by licence.

WHAT THIS CAN AND CANNOT ANSWER, because the difference is a legal one. A licence is reported only where a manifest in the repository states one: npm's lockfile records a licence per dependency from v2 onward, and package.json, Cargo.toml, pyproject.toml, pom.xml and .csproj each state the licence of the project THEY describe. Everything else is reported as unknown and counted, because guessing a dependency's licence from its name is a legal claim nobody could trace back.

Licence strings are reported exactly as the manifest wrote them. "MIT", "MIT OR Apache-2.0" and "Apache License 2.0" are three different strings and normalising the third onto an SPDX identifier would be a guess with consequences.

Parameters:

Name In Type Required Default Maximum Description
repo query string no Restrict to one repository. Repository identifier, clone URL, owner/name, or a unique name suffix.
group query string no Restrict to one repository group. It can only narrow what the principal may already see.
ecosystem query string no Only dependencies in this ecosystem. One of: python | go | rust | node | java | dotnet.
licence query string no Only dependencies carrying this licence string, matched exactly as the manifest wrote it. Pass "unknown" for the ones no manifest stated.
offset query integer no 0 How many dependency rows to skip before listing. With max_results it is a page: offset 0 is the first page, offset 50 with max_results 50 is the second. max_results is a hard maximum rather than a default, so this is the only way to reach what lies past it. It is an offset and not a cursor: each request is a fresh read, so a row added or removed between two pages may be seen twice or missed once. An offset past the end is an empty list and not an error, because total is the only way to know where the end was and it may move. It does not change total, which counts the whole set before either parameter is applied.
max_results query integer no 50 1000 Cap on returned dependency rows. The rollup counts everything regardless. A HARD MAXIMUM of 1000 rather than a default a caller raises: a larger value is clamped to it and the response says so in gaps. Page the dependencies list with offset.

Response fields:

Field Type Description
offset integer The offset this page was read at, echoed so a paging caller can check what it got rather than what it asked for.
licences array The rollup, most-declared first. Counts every dependency the principal may see and no others, over the same rows as total_dependencies and before max_results: the dependencies column sums to total_dependencies exactly, and the unknown row equals unknown_total.
  licence string The licence string, or "unknown" where no manifest stated one.
  dependencies integer Dependency declarations carrying this licence — the same unit total_dependencies counts, so these sum to it.
  distinct_packages integer Distinct (ecosystem, package) pairs carrying this licence, which is the smaller number and the one to quote for "how many packages are GPL-3.0". It does NOT sum to total_dependencies or to anything else: a package stated MIT in one repository and stated nowhere in another is distinct on two rows.
  repositories integer Repositories that declare at least one.
dependencies array The individual rows.
  repository string The repository identifier this result came from.
  commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
  ecosystem string One of the six.
  package string The package as the ecosystem names it.
  version string The pin or declared range the manifest carried, absent when it named none.
  licence string The licence, or "unknown". Never an empty string: unknown and unlicensed are different claims.
  manifest_path string The manifest this row came from, so it can be opened and checked.
  declared_by string dependency when the manifest recorded the licence of this package, project when it is the licence of the project the manifest describes.
total_dependencies integer Dependency declarations matching this query before max_results truncation — one per (repository, manifest, package), so the same package declared in three repositories counts three times. Named for its unit because the rollup beside it also counts distinct packages, and the two figures differ by more than a factor of two on a real estate.
unknown_total integer Dependency declarations whose licence no manifest stated, in the same unit as total_dependencies and equal to the rollup's unknown row. Reported separately because an audit with a large unknown count is a different conversation from a clean one, and unknown is never the stronger claim that a package is unlicensed.
notices array of string Sentences a consumer must show the reader, including which ecosystems can supply a per-dependency licence at all.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: What licences are in the estate?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/dependency-licences?max_results=2"
{"licences": [{"licence": "unknown", "dependencies": 40, "distinct_packages": 31, "repositories": 4}, {"licence": "MIT", "dependencies": 12, "distinct_packages": 9, "repositories": 3}], "dependencies": [{"repository": "example__polyglot-monorepo", "commit_sha": "4f0c…", "ecosystem": "node", "package": "lodash", "version": "4.17.20", "licence": "MIT", "manifest_path": "package-lock.json", "declared_by": "dependency"}], "total_dependencies": 52, "unknown_total": 40, "notices": ["40 dependencies of 52 carry no licence any manifest stated, and are reported as unknown rather than as unlicensed."], "gaps": []}

GET /api/v1/explain-access

Why can this principal reach this repository?

MCP tool: explain_access. Authentication: API key or OAuth access token

The audited answer to "why can Bob see this?", with the bindings that granted it or the absence of one. Available to administrators and auditors only: it discloses the policy, the repository catalogue and another principal's groups in one response.

Both outcomes are audited, including the refusal — who has been asking who can see what is itself an audit question.

Parameters:

Name In Type Required Default Maximum Description
repo query string yes Repository identifier, clone URL, owner/name, or a unique name suffix.
user query string no Whose access to explain: a subject id, a username, or an email address. Omit for the caller's own. An address nobody has signed in with yet is answered as that address with no groups, which is what that person would get on first sign-in through authz.bindings[].users.
groups query string no Comma-separated groups to evaluate against instead of the subject's. In proxy mode a subject's groups arrive on each request and are stored nowhere authoritative, so this is how the "what if they were in example-engineers" question is asked.
permission query string no Which permission to explain. Defaults to search. One of: search | view_file | view_history | view_blame | use_api | use_mcp.

Response fields:

Field Type Description
allow boolean Whether the subject holds the permission.
reason string The human-readable why.
subject string Whose access was explained.
email string The subject's email address as the authoriser saw it, which is what authz.bindings[].users and authz.adminUsers match. Empty when there is none.
groups array of string The identity groups the answer was computed from.
repository string The repository that was asked about.
repo_groups array of string The repository groups the target is in, so a denial reads without a second lookup.
matched array The bindings that granted. Empty on a denial.
  binding string The binding's name.
  via_group string The identity group that matched, or "*". Empty when the binding matched through via_user.
  via_user string The binding's users entry that matched, when the grant names the person rather than one of their groups. Omitted otherwise.
  repo_group string The repository group that carried the grant.
  repo_group_matched_by string "literal", or the glob that selected it.
  role string The role the binding granted.
  source string declarative, anonymous, default or service — a rule somebody wrote, or one the product supplied.
evaluated integer How many rules were considered, including implicit ones.
notices string Why the repository is not in the catalogue, when that is the answer — chiefly that a code-host connection retired it, with the reason and when. "No repository with that identifier is known" is true and sends somebody hunting for a typo; this says the repository was there and what removed it.

Example: Why can this person read billing-service?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/explain-access?repo=billing-service&user=a.person"
{"allow": true, "reason": "binding \"Auditors read everything with an audience\" grants viewer over example-group", "subject": "a.person", "email": "a.person@example.com", "groups": ["example-auditors"], "repository": "example__billing-service", "repo_groups": ["example-group"], "matched": [{"binding": "Auditors read everything with an audience", "via_group": "example-auditors", "repo_group": "example-group", "repo_group_matched_by": "literal", "role": "viewer", "source": "declarative"}], "evaluated": 4}

GET /api/v1/status

This instance's operational state

Not an MCP tool: operational rather than about the code: an agent asking how full the disk is has nothing useful to do with the answer, and the operator's surfaces are /api/status and /metrics Authentication: API key or OAuth access token

Configuration reload state, the licence ladder, ingestion progress, analysis coverage, the audit stream's health and disk capacity. This is where the estate's operational detail lives: the unauthenticated /api/status describes the PROCESS — build, readiness, authentication mode and which configuration file it loaded — and names no repository, no repository group and no secret fingerprint.

Parameters:

Name In Type Required Default Maximum Description
detail query string no summary How much of this report to build. summary, the default, omits the per-repository arrays (ingestion.repositories, index.repositories, analysis.repo_warnings), which are the great majority of the payload on a large estate and are not what a monitoring poll reads; full returns them. Nothing is deleted by the default: the omitted array on every response names exactly what the shape you asked for left out, and each of these blocks publishes a count at both details so an empty estate still says zero. One of: summary | full.

Response fields:

Field Type Description
detail string Which shape this response is: summary or full. Echoed so a consumer can tell a report that omitted its arrays from one whose estate is empty.
omitted array of string The field paths this shape does not carry, dotted from the root. Empty at detail=full and never null. It is what separates "not requested" from "there are none", which an absent key alone cannot say: ask again with detail=full to get them.
version object Build version, commit and build time.
config object Configuration reload state, including the last failure.
license object The licence ladder position and both meters.
ingestion object Per-repository last error, last success and failure count, plus filesSkippedByIngestion — what the INGESTION walk skipped, by reason. It is a SEPARATE number about a different walk and is NOT on searchcode_files_skipped: this walk prunes at an excluded directory and counts one event whose file count it never goes and finds out, where the analysis walk is offered every file underneath and counts each one. Read it as "what ingestion declined to hand on", never as a file count to compare with the metric.
analysis object Analysis coverage, pool depths and what the deep pass declined to read. files_skipped_by_analysis is what searchcode_files_skipped publishes — the metric is this one producer rather than a sum — and it is a LEVEL, being what the last whole-tree pass over each repository skipped, so an incremental refresh does not restate it. A repository ingestion has walked and the deep pass has not reached yet has NO series at all rather than a number in another walk's units.
audit object The audit stream's health. dropped is the field that matters.
observability object Disk capacity as last sampled, and whether this process makes any outbound connection.
index object The SEARCH index: which backend answers, per-repository coverage and warming progress. below_minimum and not_applicable are permanent, correct states rather than shortfalls. Note that /api/v1/admin's index block is a different thing — the analysis corpus — and that one is search_index there.
authz object The compiled policy's SHAPE, never its rules: compilations is what answers "is the policy I edited the policy that is running".
auth object Which authentication mode is active, and whether this instance has a login at all.
credentials object What this instance issues to agents, and whether the credential store is readable. A degraded store refuses every agent call.
advisories object The offline advisory data's age, origin and staleness, and why a bundle was refused if one was.

Example: Is this instance healthy?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/status"
{"version": {"version": "1.0.0", "commit": "cafed00d"}, "config": {"reloads": 0}, "license": {"state": "community"}, "ingestion": {"repositories": []}, "analysis": {"coverage": {"repositories": 3, "analysed": 3}}, "audit": {"dropped": 0}, "observability": {"disk": {"used_percent": 41}}}

GET /api/v1/me

Who am I, and what do I reach?

Not an MCP tool: the agent-facing equivalent is /api/credentials/self, which answers for the credential rather than the person; this adds the instance-scoped roles the account page renders and would be one more operational tool in every model's context Authentication: API key or OAuth access token

The caller's own identity, groups, instance-scoped roles and the repository groups they reach. It answers about the CALLER and about nobody else: showing you your own grants discloses nothing, because listing repositories already tells you the same thing, while a form that explained why access to an arbitrary repository was denied would confirm that repository exists.

Available to every authenticated principal, including one with no repository access at all — a person who cannot find a repository can check their own groups here before asking anybody.

Response fields:

Field Type Description
subject string The principal's stable identifier.
username string Display name, empty where the source supplies none.
email string Email address, empty where the source supplies none.
type string user, agent, service or anonymous. An agent is an MCP client acting for a person, which is what every API key and every OAuth token produces, so it is the value most callers of THIS endpoint see.
source string How this principal was established, and the one field that says WHICH of the credential mechanisms is in force. Five values, and which ones you can see depends on the listener: apikey and oauth on the API and MCP listener, proxy, local and open on the UI listener. It used to be described as "proxy, local or open", which named three of the five and excluded both values an agent can ever see on the endpoint an agent calls. /api/credentials/self answers the same question with the key id and client name beside it.
anonymous boolean True for the one anonymous principal auth.mode: open establishes.
groups array of string The identity groups this principal carries. In proxy mode they arrive on each request.
roles object The instance-scoped permissions this principal holds, one boolean per permission: administer, view_audit_log and view_analytics. They come from authz.adminGroups, authz.auditorGroups, authz.adminUsers and authz.auditorUsers and can never be reached from a binding.
repository_groups array of string The repository groups this principal reaches, derived from the repositories they can already see rather than from the policy — a directory of every declared group would name groups whose repositories are invisible to them.
repositories integer How many repositories this principal can see.

Example: What am I allowed to see?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/me"
{"subject": "a.person", "username": "a.person", "email": "a.person@example.invalid", "type": "user", "source": "proxy", "anonymous": false, "groups": ["example-engineers"], "roles": {"administer": false, "view_analytics": false, "view_audit_log": false}, "repository_groups": ["example-group"], "repositories": 2}

GET /api/v1/admin

The operator's view of this instance

Not an MCP tool: operational rather than about the code, for the same reason /api/v1/status is: an agent asking how full the disk is has nothing useful to do with the answer Authentication: API key or OAuth access token

Licence state, seats, configuration reload state, per-repository index health, corpus size, disk capacity and every issued agent credential — the one screen that answers "is this healthy?" without reading logs.

Administrators and auditors, through the instance-scoped view_analytics permission. An auditor receives the same body with can_administer false: withholding panels from them would make the read-only view a different page, and a different page is one that drifts. The repository rows are filtered to the caller's own AllowedRepos.

Parameters:

Name In Type Required Default Maximum Description
detail query string no summary How much of this report to build. summary, the default, omits the per-repository arrays (repositories, credentials, analysis.repo_warnings), which are the great majority of the payload on a large estate and are not what a monitoring poll reads; full returns them. Nothing is deleted by the default: the omitted array on every response names exactly what the shape you asked for left out, and each of these blocks publishes a count at both details so an empty estate still says zero. One of: summary | full.

Response fields:

Field Type Description
detail string Which shape this response is: summary or full. Echoed so a consumer can tell a report that omitted its arrays from one whose estate is empty.
omitted array of string The field paths this shape does not carry, dotted from the root. Empty at detail=full and never null. It is what separates "not requested" from "there are none", which an absent key alone cannot say: ask again with detail=full to get them.
version object What this process is: the build, when it started, the analyser version it runs and the grammars it links. Everything searchcode version prints, without a shell on the box.
  version string The version as git describe produced it at build time.
  described string The version as searchcode version prints it: untagged when no tag was reachable and git describe fell back to the commit.
  commit string The commit the binary was built from.
  build_time string When the binary was built.
  started_at string When this process started, RFC 3339 in UTC.
  uptime_seconds integer Seconds since started_at when this response was built.
  analyser_version integer The analyser version this binary compiled. A repository whose stored analysis is older is re-analysed; analysis.coverage is how far the estate has got.
  grammars array The tree-sitter grammars linked into this binary. Empty means a build without the grammar tags, where every outline is the line-start heuristic.
config object Configuration reload state: which file, the hash of what was loaded, when it was loaded, how many reloads succeeded, the last failure, and whether the previous configuration is still serving.
license object The licence ladder position and both meters, as searchcode license show reports them.
seats object The user meter — the one limit in this product that refuses anybody — with its approach warning and the subjects it has recently turned away.
index object The ANALYSIS corpus: repositories, how many have been analysed, files, lines and files not read by reason. It is not the search index — that is search_index below, and the two have been confused before. The name is kept because it is a published response field.
search_index object The SEARCH index: which backend answers, and the estate partitioned by per-repository index state — indexed, building, failed, below_minimum and not_applicable, plus rebuilding as a subset of indexed. The backend is chosen per repository at query time, so a mixed estate is ordinary rather than transitional. below_minimum (under min_files) and not_applicable (the backend is off) are permanent, correct answers rather than shortfalls: brute force answers those repositories correctly and neither number is one to chase. documents and bytes count only the repositories actually serving an index.
disk object Capacity as last sampled, the warn threshold, and whether ingestion is paused and why.
analysis object Analysis coverage, pool depths and what the deep pass declined to read.
audit object The audit stream's health. dropped is the field that matters.
repositories array One row per repository the caller can see, with its state, lag, last error and size.
  id string Stable identifier.
  name string Human-facing name — the normalised clone URL.
  phase string ready, cloning, pending, no_content, empty, failed or skipped.
  source string git or directory.
  last_error string The last clone or fetch failure, empty when there was none.
  consecutive_failures integer How many attempts have failed in a row.
  last_success string When it last ingested successfully, RFC 3339.
  lag_seconds number Seconds since the last SUCCESSFUL ingest, not since the last attempt.
  files_indexed integer How many files the corpus walk kept. A FILE count.
  files_skipped object Files this repository's INGESTION walk did not index, by reason. The analysis pass keeps its own count; see the analysis block.
  size_bytes integer Size on disk.
  files_skipped object Skip EVENTS by reason, and NOT a file count — a skipped directory is one event whatever it holds, because its file count is unknown without walking the thing that was skipped. It therefore does not subtract from files_indexed: a repository of 38 files can report a ledger of 12 with nothing dropped silently.
  skip_reason string Why a skipped repository was not ingested.
  search_index_state string This repository's state in the search index: indexed, building, failed, below_minimum or not_applicable. Empty means the index registry has not been told about it yet, which is not the same as unindexed.
  search_index_reason string Why it is in that state — below the size threshold, or which way a build failed. Empty for a repository that is simply indexed.
  capabilities object What this source can answer: blame, history, coupling.
credentials array Every agent credential this instance has issued, with its owner. Dormant keys are the ones to prune and this is what makes pruning happen.
  id string The credential's identifier, which is what a revoke names.
  client_name string The client it was created for.
  subject string The owning principal's identifier.
  owner string The owner's display name, from the subject record.
  email string The owner's email, from the subject record.
  groups array of string The groups frozen onto the key at issue.
  issued_at string When it was created, RFC 3339.
  expires_at string When it expires, empty for a key with no expiry.
  last_used string When it last authenticated, empty if never.
  revoked_at string When it was revoked, empty while it is live.
can_administer boolean Whether the caller may take an administrative action. An auditor is read-only, and the renderer omits every action control rather than rendering one it would reject.

Example: Is this instance healthy?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/admin"
{"version": {"version": "1.0.0"}, "config": {"reloads": 2, "failures": 1, "last_error": "unknown key authz.exceptGroups", "previous_config_still_serving": true}, "license": {"state": "valid"}, "seats": {"active": 4, "limit": 25, "recently_refused": []}, "index": {"repositories": 3, "analysed": 3, "files": 412}, "search_index": {"backend": "caisson", "enabled": true, "repositories": 3, "indexed": 1, "building": 1, "failed": 0, "below_minimum": 1, "not_applicable": 0, "min_files": 1000}, "disk": {"used_percent": 41, "warn": false}, "repositories": [{"id": "9f2c", "name": "example/billing-service", "phase": "ready", "lag_seconds": 91, "consecutive_failures": 0, "search_index_state": "indexed"}], "credentials": [{"id": "k_7f1", "client_name": "Claude Code", "subject": "a.person", "owner": "A Person"}], "can_administer": true}

GET /api/v1/usage

How this instance is being used

Not an MCP tool: the operator's own view of adoption; an agent has nothing to do with it Authentication: API key or OAuth access token

Calls by surface and tool, denials, the queries that returned nothing, and agent-instruction adoption WITH the list of repositories missing the reference.

Every repository-attributed aggregate, including the totals, is summed over the caller's own AllowedRepos. A call that touched no repository — a zero-result search, a refusal, an estate rollup — is reported in a separate unattributed block rather than folded into a total whose scope the reader would then be wrong about.

There is no per-user breakdown and there is no time series. The first is a legal position: works-council rules in Germany and France prohibit developer monitoring, and a product that ships one by default is one those customers cannot deploy. The second belongs to Prometheus — searchcode_searches_total and searchcode_mcp_calls_total are on /metrics precisely so the history survives a restart.

Parameters:

Name In Type Required Default Maximum Description
detail query string no summary How much of this report to build. summary, the default, omits the per-repository arrays (repositories, agent_instruction_coverage.missing), which are the great majority of the payload on a large estate and are not what a monitoring poll reads; full returns them. Nothing is deleted by the default: the omitted array on every response names exactly what the shape you asked for left out, and each of these blocks publishes a count at both details so an empty estate still says zero. One of: summary | full.

Response fields:

Field Type Description
detail string Which shape this response is: summary or full. Echoed so a consumer can tell a report that omitted its arrays from one whose estate is empty.
omitted array of string The field paths this shape does not carry, dotted from the root. Empty at detail=full and never null. It is what separates "not requested" from "there are none", which an absent key alone cannot say: ask again with detail=full to get them.
since string When this process started counting, RFC 3339. Nothing here is persisted.
totals object Calls, results and denials summed over the caller's repositories ONLY.
repositories array Per-repository usage, busiest first.
  repository string The repository identifier.
  name string Its human-facing name.
  calls integer How many calls read it.
  results integer How many results those calls returned.
  by_tool object The same count broken down by tool.
unattributed object Calls that named no repository at all. Labelled rather than folded into totals.
calls_by_surface object Instance-wide call counts by surface: api, mcp or ui. Not repository-attributed, and named so.
calls_by_tool object Instance-wide call counts by tool.
denials_by_surface object Authorisation refusals by surface.
invalid_calls_by_tool object Calls refused as malformed — an unknown or unparseable parameter — by tool. The direct measure of a parameter the tool descriptions failed to teach.
tools_never_called array of string Every catalogue tool with no call since since, stated rather than left to be noticed missing from calls_by_tool.
first_tool_of_session object The first tool called in each MCP session, counted by tool and summed across sessions. REST calls have no session and are not in it.
tool_transitions array Which tool followed which within an MCP session, most frequent first, summed across sessions. search followed by search is the over-searching the search tool's description warns against; search followed by read_file is the ladder working.
  from string The earlier call's tool.
  to string The next call's tool, in the same session.
  count integer How often.
tool_transitions_total integer How many distinct transitions exist; tool_transitions carries the top 25.
sessions_untracked integer MCP sessions the tracking bound turned away. Non-zero means first_tool_of_session and tool_transitions are partial.
zero_result_queries array Queries that matched nothing, most frequent first. Every one is either a coverage gap or a ranking gap and both are fixable.
  query string The query text.
  count integer How often it returned nothing.
zero_result_total integer Every miss, including ones whose text was not retained.
queries_retained boolean audit.logSearchQueries. False is why the table above may be empty while the total is not.
agent_instruction_coverage object The proportion of the caller's repositories whose agent-instruction file names searchcode, WITH the ones that do not. A ratio with no list tells an operator they have a problem and not what to do about it.
  repositories integer How many were sampled.
  covered integer How many reference searchcode.
  ratio number covered divided by repositories; zero on an empty estate.
  missing array of string The repositories to edit.
per_user_breakdown boolean Whether this instance reports work per PERSON. False unless the operator has enabled author attribution (analytics.attributeAuthors); see that setting. It is a LIVE READ of the switch rather than a constant, so what you read here is a fact about this instance today and not a promise about the product. False on every default install.

Example: What are people failing to find?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/usage"
{"since": "2026-01-01T00:00:00Z", "totals": {"calls": 41, "results": 388, "denied": 0}, "repositories": [{"repository": "9f2c", "name": "example/billing-service", "calls": 30, "results": 300, "by_tool": {"search": 28, "read_file": 2}}], "unattributed": {"calls": 6, "results": 0, "denied": 2}, "calls_by_surface": {"api": 12, "mcp": 25, "ui": 10}, "calls_by_tool": {"search": 38}, "denials_by_surface": {"api": 2}, "zero_result_queries": [{"query": "circuitbreaker", "count": 4}], "zero_result_total": 6, "queries_retained": true, "agent_instruction_coverage": {"repositories": 3, "covered": 1, "ratio": 0.3333, "missing": ["example/billing-service", "example/docs-drop"]}, "per_user_breakdown": false}

GET /api/v1/openapi.json

The OpenAPI document for this API

Not an MCP tool: the specification of the surface rather than a call against it Authentication: None — an integrator evaluating the product has no credential yet, and a reference they cannot read is not a reference; it is generated from route definitions and discloses nothing about the estate

Generated from the same definitions the handlers and the MCP tool catalogue are generated from, so the three cannot disagree. Import it into a client generator, Postman, Insomnia or Bruno.

Unauthenticated, because the reader is deciding whether to integrate and does not have a credential yet. It is generated from route and schema definitions rather than from the estate, so it names no repository, no user and no finding.

Response fields:

Field Type Description
openapi string The OpenAPI version this document conforms to.
info object Title, version and description of the API.
paths object One entry per endpoint.
components object The shared error schema and the gaps schema.

Example: Fetch the spec without a credential

curl -sS \
  "$SEARCHCODE_URL/api/v1/openapi.json"
{"openapi": "3.0.3", "info": {"title": "searchcode", "version": "1"}, "paths": {"/api/v1/search": {"post": {"operationId": "search"}}}}

GET /api/v1/docs

The rendered API reference

Not an MCP tool: a page rather than a call Authentication: None — the reader is deciding whether to integrate and has no credential yet; it is rendered from route definitions and discloses nothing about the estate

The human-readable reference, server-rendered from the same in-memory specification openapi.json is generated from. No CDN, no viewer bundle and nothing fetched at page load: a reference that needs the internet is a blank page in an air-gapped install. It works with JavaScript disabled, because every endpoint, parameter and example is in the served HTML rather than assembled in the browser.

Response fields:

Field Type Description
body string The rendered HTML reference.

Example: Read the reference in a browser

curl -sS \
  "$SEARCHCODE_URL/api/v1/docs"
<h1>searchcode API</h1> … one section per endpoint, every parameter described, every example worked.

Files

GET /api/v1/repos/{repo}/file

Read a file, or a range of lines from one

MCP tool: read_file. Authentication: API key or OAuth access token

Returns the file's lines as JSON, with the total length so a caller reading a range still knows how much it did not read. Use start and end to read around a search match rather than pulling a whole file into a context window.

Pass ref to read the file as it stood at one commit. Omitting it reads the checked-out revision, whose SHA every response carries — so a result can be re-read exactly later rather than approximately.

Parameters:

Name In Type Required Default Maximum Description
repo path string yes Repository identifier, clone URL, owner/name, or a unique name suffix.
path query string yes File path relative to the repository root.
start query integer no First line to return, 1-based and inclusive. Omit for the start of the file.
end query integer no Last line to return, 1-based and inclusive. Omit for the end of the file.
ref query string no Read the file at this commit rather than at the checked-out revision. A 7-to-40 character hexadecimal SHA; a branch or tag name is refused, because a link that pins a branch drifts to different lines as the branch moves. A source with no commits, or a revision a shallow clone never fetched, reports a gap and returns the current content rather than failing.

Response fields:

Field Type Description
repository string The repository identifier this result came from.
commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
repository_name string The repository's own name — its normalised clone URL, or its path for a directory source. This is what to show a person; repository is the identifier to pass back.
path string The path that was read.
language string The language scc detected.
lines array of string The returned lines, without their terminators.
start integer 1-based line number of the first returned line.
total_lines integer The file's full length.
truncated boolean The file was longer than the reader would return.
pinned boolean The ref that was asked for is a past revision, not the checked-out one, so the content is not what the repository holds now. A caller cannot infer this from commit_sha, which reports the ref that was read either way. It says nothing about where the lines came from: a read at the checked-out revision is false and is as exact as any other. Read immutable for that.
immutable boolean The lines were read out of the commit object commit_sha names, so they are exactly that commit's content and cannot change. This is the field to cite a range by. False for a source with no commits, for a symbolic link, and for a read that could not reach the commit object, which says why in gaps.
notices array of string Sentences to read before the lines. Empty for most files; carries the risk advisory when this file is in its repository's top 10% for complexity, churn, distinct authors or production findings, or when one of those could not be measured. It states care, never consequence.
risk object Present only beside a risk advisory: the factors behind it, whole-file figures from the table built after the last index. Absent means nothing here is elevated or unknown, not that nothing was measured.
  available boolean False when no factors exist for this file yet; every other member is then absent.
  commit_sha string The commit the analysis described, or "unavailable" for a source with no commits.
  scope string Always file: every figure is about the whole file, never a symbol in it.
  complexity integer The file's cyclomatic complexity, from scc.
  churn integer Commits touching the file inside the window. Null when UNKNOWN, never zero.
  authors integer DISTINCT authors of those commits — a count, never a name. Null when UNKNOWN, and null beside authors_band when the count is below the anonymity floor.
  authors_band string few when the distinct count is below the instance's anonymity floor; absent otherwise.
  findings integer Findings in production code. Null when UNKNOWN, never zero.
  test_findings integer Findings in test files, reported beside and never counted in findings.
  percentiles object Each KNOWN factor's percentile within its repository, 0 to 100: the share of files with a strictly lower value. An unknown factor has no entry.
  score number The mean of the known percentiles. The order, not a measurement.
  elevated array of string Factors this file is in its repository's top 10% for.
  unknown array of string Factors that could not be measured. Never read an unknown factor as low.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: Read around a match

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/repos/deploy-tool/file?path=retry.go&start=10&end=14"
{"repository": "example__deploy-tool", "commit_sha": "4f0c...", "repository_name": "https://example.invalid/deploy-tool", "path": "retry.go", "language": "Go", "lines": ["// RetryWithBackoff retries with exponential backoff.", "func RetryWithBackoff(ctx context.Context, attempts int) error {"], "start": 10, "total_lines": 48, "truncated": false, "pinned": false, "immutable": true, "notices": [], "gaps": []}

POST /api/v1/repos/{repo}/files

Read several files from one repository in one call

MCP tool: read_files. Authentication: API key or OAuth access token

The batch form of read_file, bounded. One round trip instead of ten is the difference between an agent that reads what it needs and one that reads the first file and guesses.

Each entry reports its own error rather than failing the batch: a path that does not exist leaves the other files readable.

Parameters:

Name In Type Required Default Maximum Description
repo path string yes Repository identifier, clone URL, owner/name, or a unique name suffix.

Request body:

Name In Type Required Default Maximum Description
paths body array of string yes The file paths to read, relative to the repository root. Bounded at 50 per call.
start body integer no First line to return from each file, 1-based and inclusive.
end body integer no Last line to return from each file, 1-based and inclusive.

Response fields:

Field Type Description
files array One entry per requested path, in the order asked.
  repository string The repository identifier this result came from.
  commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
  path string The requested path.
  language string The language scc detected.
  lines array of string The returned lines.
  start integer 1-based line number of the first returned line.
  total_lines integer The file's full length.
  truncated boolean The file was longer than the reader would return.
  pinned boolean Always false here: this endpoint reads the checked-out revision. Present because each entry has the file endpoint's shape.
  immutable boolean The lines were read out of the commit object commit_sha names, so they are exactly that commit's content. False for a source with no commits, a symbolic link, or a read that could not reach the commit object.
  error string Why this one path could not be read. Present only on failure, and it does not fail the other entries.
  error_code string The semantic class of this entry's failure, from the same set as a top-level error_code.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: Read the two files a search pointed at

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  -X POST \
  -H 'Content-Type: application/json' \
  -d '{"paths": ["retry.go", "platform.go"]}' \
  "$SEARCHCODE_URL/api/v1/repos/deploy-tool/files"
{"files": [{"repository": "example__deploy-tool", "commit_sha": "4f0c...", "path": "retry.go", "language": "Go", "lines": ["package platform"], "start": 1, "total_lines": 48, "truncated": false}, {"repository": "example__deploy-tool", "commit_sha": "4f0c...", "path": "platform.go", "language": "Go", "lines": ["package platform"], "start": 1, "total_lines": 30, "truncated": false}], "gaps": []}

GET /api/v1/repos/{repo}/raw

Fetch a file's bytes unrendered

Not an MCP tool: a browser affordance rather than an agent one; an agent wants read_file's structured lines, and a tool returning arbitrary undecoded bytes into a context window is a hazard rather than a feature Authentication: API key or OAuth access token

The browser affordance behind "view raw". It returns the file's bytes with the DECLARED content type text/plain and never the detected one.

Every repository in the estate is untrusted input by definition — the product ingests whatever the estate contains — so any HTML or SVG among it is stored XSS against every user of the instance if it is served as its apparent type. The response therefore always carries text/plain, X-Content-Type-Options: nosniff, Content-Disposition: attachment and a Content-Security-Policy of default-src 'none'; sandbox. There is no exception list, because the exception list is the vulnerability.

Prefer read_file for anything programmatic: it returns structured lines and the file's length.

A ref this endpoint cannot honour is REFUSED rather than answered with the checked-out revision's bytes. Every other endpoint reports a pin it could not resolve in gaps beside the content it could still serve; a response that IS bytes has nowhere to put one, and a download that is silently a different revision from the one the URL names is the failure this parameter exists to remove.

Parameters:

Name In Type Required Default Maximum Description
repo path string yes Repository identifier, clone URL, owner/name, or a unique name suffix.
path query string yes File path relative to the repository root.
ref query string no Download the bytes as they stood at this commit rather than at the checked-out revision. A 7-to-40 character hexadecimal SHA; a branch or tag name is refused, for the reason read_file gives. Unlike read_file, a revision that cannot be read is a 404 rather than the current content with a gap: the response is bytes and has nowhere to say which revision they came from, so serving the wrong ones would be indistinguishable from serving the right ones.

Response fields:

Field Type Description
body string The file's bytes, served as text/plain with nosniff and an attachment disposition regardless of the file's extension or content.

Example: Download a file unrendered

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/repos/untrusted-content/raw?path=payload.html"
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Content-Disposition: attachment; filename="payload.html"
Content-Security-Policy: default-src 'none'; sandbox

<script>alert(1)</script>

GET /api/v1/repos/{repo}/tree

List a directory inside a repository

MCP tool: file_tree. Authentication: API key or OAuth access token

One level by default, or the whole subtree with recursive=true. Cheaper than reading files and the right way to find out what a repository contains before deciding what to read.

Parameters:

Name In Type Required Default Maximum Description
repo path string yes Repository identifier, clone URL, owner/name, or a unique name suffix.
path query string no Directory to list, relative to the repository root. Omit for the root.
recursive query boolean no false Descend the whole subtree rather than listing one level.
max_entries query integer no 10000 Cap on returned entries. Truncated says whether the cap was reached.

Response fields:

Field Type Description
repository string The repository identifier this result came from.
commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
repository_name string The repository's own name — its normalised clone URL, or its path for a directory source. This is what to show a person; repository is the identifier to pass back.
path string The directory that was listed.
entries array The listing, sorted by path.
  path string Path relative to the REPOSITORY root, not to the listed directory.
  dir boolean True for a directory.
  size integer Size in bytes, zero for a directory.
truncated boolean The listing hit max_entries.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: What is in this repository?

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/repos/billing-service/tree"
{"repository": "example__billing-service", "commit_sha": "9b1c...", "repository_name": "https://example.invalid/billing-service", "path": "", "entries": [{"path": "billing.go", "dir": false, "size": 1820}, {"path": "go.mod", "dir": false, "size": 64}], "truncated": false, "gaps": []}

GET /api/v1/repos/{repo}/symbols

The declaration outline of one file, or of a whole repository

MCP tool: symbols. Authentication: API key or OAuth access token

The functions, types and other declarations a file defines, with their lines. OMIT path AND YOU GET THE WHOLE REPOSITORY'S declarations instead, in path then line order — one call rather than one per file.

The two scopes answer from different places and the difference is visible in the gaps. One file is opened, so a language no grammar in this build covers is still answered by the line-start heuristic. A repository is not read file by file: the repository-wide outline is the parsed table the analysis pass left behind, so a file the grammars do not cover contributes nothing and is COUNTED IN A GAP rather than quietly omitted, and a repository whose deep pass has not run yields a gap and no declarations rather than an empty outline.

Every symbol carries a confidence, and it means exactly one thing. "exact" is a declaration a tree-sitter grammar resolved during the analysis pass, and it carries a real kind — method, class, interface, constructor. "probable" is a line-start heuristic, and its kind is the undifferentiated "declaration". There is no third value.

Which languages parse is a property of this build rather than of the request: ten grammars ship, and a file in any other language is answered by the heuristic with a gap saying what it could not resolve. Nothing is parsed while you wait — the parse happens once, at ingest, so a repository whose analysis pass has not finished is answered by the heuristic too.

Parameters:

Name In Type Required Default Maximum Description
repo path string yes Repository identifier, clone URL, owner/name, or a unique name suffix.
path query string no File path relative to the repository root. OMIT IT for every declaration in the repository, in path then line order.
offset query integer no 0 How many declarations to skip before listing. With max_results it is a page: offset 0 is the first page, offset 50 with max_results 50 is the second. max_results is a hard maximum rather than a default, so this is the only way to reach what lies past it. It is an offset and not a cursor: each request is a fresh read, so a row added or removed between two pages may be seen twice or missed once. An offset past the end is an empty list and not an error, because total is the only way to know where the end was and it may move. It does not change total, which counts the whole set before either parameter is applied.
max_results query integer no 2000 5000 Cap on returned declarations. The default is generous because one file's outline is a complete answer at any sane length and a repository's is not; total says how many there were before it bit. It is a HARD MAXIMUM of 5000 rather than a default a caller raises — five times every other endpoint's, for the same reason the default is — and offset is what reaches a repository declaring more.

Response fields:

Field Type Description
offset integer The offset this page was read at, echoed so a paging caller can check what it got rather than what it asked for.
symbols array The declarations, in line order.
  repository string The repository identifier this result came from.
  commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
  name string The declared name.
  kind string What was declared. A parsed symbol carries a real kind — function, method, constructor, class, interface, struct, enum, trait, type, module, property, macro, impl, constant or variable. A heuristic guess carries "declaration", which is the one kind it can tell you and is why the confidence beside it is probable.
  path string The file the declaration is in.
  line integer 1-based line of the declaration's NAME.
  end_line integer 1-based last line of the declaration. PRESENT ONLY WHEN IT WAS MEASURED, which means a grammar read the file — so a probable symbol never carries one. It is omitted rather than sent as zero: a zero reads as a measurement, and a caller ranking by length would sort every unmeasured declaration to the top of the shortest list.
  lines integer How long the declaration is, from its name to its end. Omitted alongside end_line for the same reason. It is one short of the source text for a form that carries something ABOVE its name, such as a decorated Python method, because line is where the name is.
  language string The language scc detected.
  confidence string exact when a parser resolved it, probable when a heuristic did. There is no third value: "unknown" would be a way of avoiding the decision.
total integer Declarations found before max_results truncation.
truncated boolean The list was cut at max_results, so total is larger than what is here.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: Outline before reading

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/repos/billing-service/symbols?path=billing.go"
{"symbols": [{"repository": "example__billing-service", "commit_sha": "9b1c...", "name": "Charge", "kind": "function", "path": "billing.go", "line": 14, "language": "Go", "confidence": "probable"}], "total": 1, "truncated": false, "gaps": []}

History

GET /api/v1/repos/{repo}/blame

Per-line authorship for one file

MCP tool: blame. Authentication: API key or OAuth access token

Who last touched each line, and when.

Use start and end to blame a range rather than a whole file — the question is nearly always "who wrote this bit", and they are the same two parameters read_file takes, with the same 1-based inclusive meaning. The line numbers on the rows are the file's own either way, so a range narrows the answer without renumbering it.

A directory source has no commits, so blame over one returns HTTP 200 with an empty lines array and a POPULATED gaps array — never a 500 and never a bare empty result. "No blame data available: this source has no history" is a different sentence from "nobody has touched this file".

Pass ref to read authorship as it stood at one commit. Omitting it blames the checked-out revision. A ref this clone does not have, or one the file did not exist at, reports a gap and no lines rather than the current revision's authorship — which would attribute these lines to the wrong commits and the wrong people.

There is no search over history: no pickaxe, no date range. blame says when a line LAST changed, not when it first appeared. A line introduced in one commit and reformatted in a later one blames to the later one, with nothing on this response saying so, so this is not a date-of-first-appearance and commit_log cannot make it into one. Ask your code host for that.

Parameters:

Name In Type Required Default Maximum Description
repo path string yes Repository identifier, clone URL, owner/name, or a unique name suffix.
path query string yes File path relative to the repository root.
start query integer no First line to return, 1-based and inclusive. Omit for the start of the file.
end query integer no Last line to return, 1-based and inclusive. Omit for the end of the file.
ref query string no Compute authorship at this commit rather than at the checked-out revision. A 7-to-40 character hexadecimal SHA; a branch or tag name is refused, for the reason read_file gives. A commit this clone does not have reports a gap and no lines.

Response fields:

Field Type Description
repository string The repository identifier this result came from.
commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
path string The file that was blamed.
lines array One entry per line, in order.
  line integer 1-based line number.
  commit_sha string The commit that last touched the line.
  author string The author of that commit.
  when string RFC 3339 timestamp of that commit.
pinned boolean Authorship was computed at the ref that was asked for rather than at the checked-out revision. A caller cannot infer this from commit_sha, which reports the revision that was blamed either way.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: Blame over a directory source reports a gap, not an error

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/repos/docs-drop/blame?path=README.md"
{"repository": "example__docs-drop", "commit_sha": "unavailable", "path": "README.md", "lines": [], "pinned": false, "gaps": [{"capability": "history", "reason": "source type 'directory' has no history", "sources": ["example__docs-drop"], "source_count": 1, "kind": "unanswered"}]}

GET /api/v1/repos/{repo}/log

Commits touching a file, newest first

MCP tool: commit_log. Authentication: API key or OAuth access token

The change history of one path, newest first.

REQUIRES HISTORY. A shallow clone has one commit and says so through gaps; a directory source has none and says so the same way. Neither returns a short history and leaves the caller to read it as a quiet file — "this clone holds one commit" and "this path has changed once" are different statements, and only the second is about the code.

It lists the commits that TOUCHED the path, never which one introduced a given line. There is no search over history: no pickaxe, no date range. blame says when a line LAST changed, not when it first appeared. A line introduced in one commit and reformatted in a later one blames to the later one, so the two endpoints do not compose into a date-of-first-appearance. Ask your code host for that.

Parameters:

Name In Type Required Default Maximum Description
repo path string yes Repository identifier, clone URL, owner/name, or a unique name suffix.
path query string no File path relative to the repository root. Omit for the repository's own history.
limit query integer no 50 1000 How many commits to return.
offset query integer no 0 How many commits to skip before listing. With max_results it is a page: offset 0 is the first page, offset 50 with max_results 50 is the second. max_results is a hard maximum rather than a default, so this is the only way to reach what lies past it. It is an offset and not a cursor: each request is a fresh read, so a row added or removed between two pages may be seen twice or missed once. An offset past the end is an empty list and not an error, because total is the only way to know where the end was and it may move. It does not change total, which counts the whole set before either parameter is applied.

Response fields:

Field Type Description
repository_name string The repository's own name — its normalised clone URL, or its path for a directory source. This is what to show a person; repository is the identifier to pass back.
commits array The commits, newest first.
  repository string The repository identifier this result came from.
  commit_sha string The commit the result was read at, or "unavailable" for a directory source, which has no commits. Never an empty string: a missing SHA is a defect, an unavailable one is a property of the source.
  author string The commit's author.
  message string The commit subject line.
  when string RFC 3339 timestamp.
total integer Commits touching this path before the limit was applied, so a page of commits states what it is a page OF. It is NOT the length of commits — compare the two to see how much the limit left out. Absent, rather than zero, in the rare case the count could not be established; a history nobody could count is not a history of no changes.
truncated boolean More commits touch this path than limit returned. False means commits holds the whole history of this path, which is the only state in which "last changed" or "how often does this churn" can be answered from it.
offset integer The offset this page was read at, echoed so a paging caller can check what it got rather than what it asked for.
gaps array Capabilities that could not be answered for part of this request. An empty results array with an empty gaps array means nothing matched; an empty results array with a populated gaps array means the question could not be answered. Always check it.
  capability string What could not be answered: history, blame, dependencies, findings, symbols.
  reason string Why, in prose an operator can act on — e.g. "source type 'directory' has no history".
  sources array of string What the gap is true of: repository identifiers, ascending. One gap stands for every repository the same capability failed for the same reason, so this names them — capped at the first few, because an estate of two thousand directory sources would otherwise put two thousand identifiers in every response. Read source_count for how many there actually are; it is exact even when this list is cut.
  source_count integer How many sources this gap is true of, EXACT and never capped. Larger than the length of sources means the list was cut and this is the real total — never derive the scale by counting sources.
  kind string "unanswered" when the answer is incomplete and something could make it complete — a shallow clone, an analysis pass that has not finished. "inapplicable" when the answer is complete and the question does not apply to this input, such as declarations in a Markdown file. Both are reported; neither is an empty result.

Example: Recent changes to a file

curl -sS \
  -H "Authorization: Bearer $SEARCHCODE_TOKEN" \
  "$SEARCHCODE_URL/api/v1/repos/deploy-tool/log?path=orders.go&limit=2"
{"repository_name": "https://example.invalid/deploy-tool", "commits": [{"repository": "example__deploy-tool", "commit_sha": "b2f1...", "author": "Fixture Author", "message": "add currency", "when": "2021-02-01T00:00:00Z"}], "total": 9, "truncated": true, "gaps": []}

MCP tools

These 27 MCP tools are what tools/list returns, to every principal. Each one calls the endpoint beside it, with the same authorisation, limits and audit record.

Tool Endpoint
analyze GET /api/v1/repos/{repo}/analyze
blame GET /api/v1/repos/{repo}/blame
change_coupling GET /api/v1/repos/{repo}/coupling
commit_log GET /api/v1/repos/{repo}/log
complexity_trend GET /api/v1/repos/{repo}/complexity-trend
definitions GET /api/v1/repos/{repo}/definitions
estate_definitions GET /api/v1/definitions
estate_references GET /api/v1/references
estate_summary GET /api/v1/estate
explain_access GET /api/v1/explain-access
file_complexity GET /api/v1/repos/{repo}/complexity
file_dependencies GET /api/v1/repos/{repo}/dependencies
file_technologies GET /api/v1/repos/{repo}/technologies
file_tree GET /api/v1/repos/{repo}/tree
find_usages POST /api/v1/find-usages
find_vulnerable_usages POST /api/v1/vulnerabilities
findings GET /api/v1/findings
hotspot_trend GET /api/v1/hotspot-trend
hotspots GET /api/v1/repos/{repo}/hotspots
licence_audit GET /api/v1/dependency-licences
list_repositories GET /api/v1/repos
read_file GET /api/v1/repos/{repo}/file
read_files POST /api/v1/repos/{repo}/files
references GET /api/v1/repos/{repo}/references
risk_ranking GET /api/v1/risk
search POST /api/v1/search
symbols GET /api/v1/repos/{repo}/symbols

Errors

Every error carries error and error_code, and the response carries an X-Request-Id header. Quote it in a support conversation: the log line for the same request has the detail that is not in the body.

Status error_code Meaning
400 invalid_params A parameter was missing, malformed or not recognised. Unknown parameters are REFUSED rather than ignored: silently accepting one is how a caller comes to believe it is tuning something.
401 unauthenticated No credential was presented, or the credential did not resolve to a principal. Present an API key or an OAuth access token; this listener does not accept session cookies.
403 forbidden The principal can see the repository and does not hold the permission this call needs. A repository the principal cannot see returns repo_not_found instead, because a 403 would confirm that it exists.
404 repo_not_found No repository of that name is available to this principal. This is also the answer for a repository that exists and is invisible, deliberately.
404 wrong_origin Raw file content is served only from the hostname api.rawContentOrigin names, and this request arrived at a different one. Nothing is wrong with the repository, the file or the credential: the same content is available as structured lines from the file endpoint on any origin.
404 file_not_found The repository is visible and holds no file at that path. On the raw endpoint it is also the answer for a ref whose bytes could not be read: that response is a file and has nowhere to report a gap, so a revision it cannot serve is refused rather than answered with the checked-out one.
429 rate_limited The credential exceeded its request ceiling. Retry-After carries the interval, and retry_after_seconds repeats it in the body.
500 internal_error Something failed inside the instance. The log line for the request carries the detail; quote the X-Request-Id header from the response.
499 client_closed The caller closed the connection before the instance finished answering. Nothing about the request was rejected and nothing failed inside the instance: this is reported so that an abandoned request is not counted as a malformed one. A client that times out on slow searches is the usual cause.
422 repo_empty The repository cloned successfully and has no commits, so it has no tree to read. Nothing about the request was wrong and nothing failed inside the instance; the repository is checked again on its ordinary refresh interval and becomes readable once something is pushed to it.
503 upstream_unavailable The code host refused or dropped this repository's clone — throttling, a server error, a reset connection or a truncated pack — so it has never been ingested. This is the code host declining, not a fault in the repository or the instance, and ingestion retries on its own backoff. An authentication failure is NOT reported this way, because a missing, a private and a throttled repository can answer identically.

Generated from the documentation that ships with searchcode enterprise. Questions: ben@boyter.org