Code search, blast radius and data model
Search indexed repositories, see what a change touches, and read the data model parsed from ORM definitions and SQL.
On for every workspace. The code routes work for every repository your
workspace connected through GitHub once its index is ready. Each route is
declared under the code_intelligence plan feature. A workspace whose plan
lacks it can get 403 INSUFFICIENT_PERMISSIONS with details.feature set to
code_intelligence; handle it.
nima keeps its own index of the repositories your workspace connected through
GitHub. Three routes read it with an API key or OAuth token, all with scope
knowledge:read: one finds files, one says what a change touches, and one
returns the tables and columns the code declares. None of them returns source
code. Read the code from your own checkout, using the paths and line numbers
these routes give you. How the index is built is explained in
Code intelligence.
Code search, blast radius and data model routes
The three code intelligence routes, their scope and the MCP tool that calls each one:
| Method | Path | Scope | MCP tool |
|---|---|---|---|
GET | /v1/code-intel/search | knowledge:read | search_code |
GET | /v1/code-intel/blast-radius | knowledge:read | blast_radius |
GET | /v1/code-intel/data-model | knowledge:read | get_data_model |
Paths are relative to https://api.getnima.io. All three are on the soft rate
limit tier, 300 requests per 10 seconds. See
Rate limits and
Scopes and permissions.
Search the codebase
/v1/code-intel/searchknowledge:readMCP toolsearch_codeSearch the workspace's indexed repositories for files that answer a question.
Every repository in the workspace is searched unless you name one. Only
repositories whose indexStatus is ready are searched, and every repository
comes back in repositories whatever its state, so one response tells you what
was searched and what was skipped.
Each repository gets its own 45 second budget, and they run in parallel. A
repository that overruns or fails is named in droppedRepositories instead of
sinking the call, so a thin result set is not proof that the code is not there.
qstringrequiredsearch_code tool caps it at 300.repositoryIdstringrepositories in an earlier response. Omit it to search every repository.limitinteger8, maximum 20.limit is clamped rather than rejected: 50 becomes 20, a negative number
becomes 1, and 0 or a non-number becomes 8. It applies to each repository
and again to the merged list, so you never get more than limit hits in total.
There is no pagination and no offset: raise limit to 20, or ask a narrower
question.
A search that finds nothing is a 200 with an empty results array, and so is
a workspace where no index is ready yet.
Errors from the search route
| Status | error | When |
|---|---|---|
400 | VALIDATION_ERROR | q is missing or under 2 characters once trimmed. |
401 | TOKEN_INVALID | The credential is missing, unknown, revoked or expired. |
403 | INSUFFICIENT_PERMISSIONS | The credential lacks knowledge:read, or the plan gate applies (see the callout at the top), or the token has no workspace (Must belong to an organization). |
404 | RESOURCE_NOT_FOUND | repositoryId belongs to another workspace (Repository not found), or to nothing (RepositoryCloneEntity not found). |
429 | RATE_LIMITED | Over the soft tier. Retry-After carries the seconds to wait. |
500 | INTERNAL_SERVER_ERROR | repositoryId is not a UUID. |
repositoryId is not checked for shape on this route. A value that is not a
UUID reaches the database and fails there, so you get a 500 with an
incidentId where a 400 belongs. Send an id you read from a repositories
array and this never happens. The two 404 texts differ, and the second is an
internal name that leaked; match on the status, never on message.
The search response fields
querystringq, trimmed.repositoriesobject[]id, repoFullName, indexStatus (pending, indexing, ready or failed; only ready is searched), lastCommitSha (the 40 character commit the index was built from, null before the first index), lastSyncedAt (ISO 8601 UTC or null) and indexWarnings (tokens naming a stage of the last run that did not fully complete; empty when clean).droppedRepositoriesstring[]ready repositories whose search failed or ran past 45 seconds. Their hits are missing from results.resultsobject[]Each entry in results has these fields.
repositoryIdstringrepoFullNamestringowner/name.filePathstringscorenumbersummarystring | nullnull when the file has none.matchedChannelsstring[]matchenumexact, semantic or none. Read it as a verdict on the literal you asked for; see below.symbolsobject[]name, kind, signature (or null), lineStart and lineEnd (from 1).match only carries meaning when the query looks like a path (it has a / or
ends in a file extension) or a single identifier (one token, optionally with
()). For those, exact means the hit's path equals the path you gave, or the
file declares a symbol with that name. semantic means a path or symbol channel
matched but the hit is not the literal. none means no channel confirmed the
literal: the hit is only a neighbour, and the thing you named may not exist. Any
other query, such as a plain-English question, is always semantic.
symbols[].kind is one of function, class, interface, type, enum,
route, entity or decorator. symbols is the file's own list, not the
symbols that matched, and a file declaring more than eight shows an arbitrary
eight.
lastCommitSha is the commit the index was built from. Compare it to the
commit you are working on before you trust anything structural: a claim that
something does not exist, a line range. The index follows each repository's
default branch only and updates when a push lands on it, so work on a feature
branch is invisible until it merges.
Get the blast radius of a change
/v1/code-intel/blast-radiusknowledge:readMCP toolblast_radiusGiven files, symbols or both, return what a change there reaches: the modules that depend on it, the dependent files by distance, the tests that cover it, and the spec documents that govern it. It is read from the existing index and the knowledge graph's code links, with no re-index and no model call, so an index behind your commit answers about the commit it was built from.
Pass a pull request's changed files as paths to review its reach, or a symbol
name to answer "who depends on this" before you edit.
pathsstringa.ts,b.ts). A shorter suffix of an indexed path also matches. The MCP tool takes an array of at most 50 items, each at most 400 characters, and joins it for you.symbolstringrepositoryIdstringdepthinteger2, maximum 4; a larger value is clamped to 4.modestringagent is accepted, for parity with ask_knowledge_graph. It changes nothing: the response is already compact JSON.At least one of paths and symbol is required, and together they may name at
most 50 targets. Duplicates and blank entries are dropped before counting.
The blast radius response
targetsobject[]repositoryId, repoFullName, filePath, fileId.unresolvedstring[]targets is a 200, not an error.depthintegerdependentModulesobject[]moduleId, moduleName, repositoryId, repoFullName, summary, edgeCount, relations, depth.dependentFilesobject[]repositoryId, repoFullName, filePath, depth (1 means it imports a target directly), relations, moduleName and callers.dependentFiles[].callersobject[]symbol, lineStart, lineEnd, and calls (the target symbol, or null).coveringTestsobject[]dependentFiles. A path counts as a test when a directory in it is __tests__, tests or test, or the file name ends in .spec or .test with a JS or TS extension.governingSpecsobject[]documentId, title, status, updatedAt, viaEntities.callGraphobjectcoveredTargets, importOnlyTargets and a note. Call edges exist only for .ts, .tsx, .js, .jsx, .mjs and .cjs. For a target in any other language, an empty callers means no parser, not no callers.truncatedbooleantrue when the walk hit its 400 file cap. The answer is then a prefix, not the whole set.Errors from the blast radius route
| Status | error | When |
|---|---|---|
400 | VALIDATION_ERROR | Neither paths nor symbol is given (paths or symbol is required), more than 50 targets (at most 50 paths and symbols per call), or mode is not agent. |
401 | TOKEN_INVALID | The credential is missing, unknown, revoked or expired. |
403 | INSUFFICIENT_PERMISSIONS | Missing knowledge:read, the plan gate, or no workspace on the token. |
404 | RESOURCE_NOT_FOUND | repositoryId belongs to another workspace or to nothing. |
429 | RATE_LIMITED | Over the soft tier. |
500 | INTERNAL_SERVER_ERROR | repositoryId is not a UUID, as on search. |
curl -s -G https://api.getnima.io/v1/code-intel/blast-radius \
-H "Authorization: Bearer $NIMA_API_KEY" \
--data-urlencode "paths=src/upload/upload.guard.ts,src/upload/limits.ts" \
-d "depth=2"Read the data model
/v1/code-intel/data-modelknowledge:readMCP toolget_data_modelReturn the tables, collections, columns, relations, indexes and enums that a repository's ORM definitions and SQL declare, each with the file and line it is declared at. The model is parsed at index time with no model call, so it is deterministic for a given commit.
Fourteen ORMs are parsed: Prisma, Drizzle, TypeORM, Sequelize, MikroORM and
Mongoose for TypeScript and JavaScript; SQLAlchemy, SQLModel, Django, Peewee and
Tortoise for Python; GORM, ent and Bun for Go. SQL CREATE TABLE and
ALTER TABLE statements in migrations and schema files are parsed too, and an
ORM table shadows a SQL table of the same name. An empty model means no
supported ORM or DDL was found, not that the app stores no data.
repositoryIdstringtablestringusers, User or billing.invoices. Case-insensitive, at most 200 characters. Omit it for a summary of every table.The data model response in summary mode
Without table, the response is { table: null, repositories: [...] } with one
entry per repository. Each entry has mode: "summary", the repository fields
below, and tables, capped at 400 per repository with truncated set when more
exist. A summary table carries name, schema, entityName (the declaring
class, null for SQL), orm, kind (table, view, collection or
embedded), filePath, lineStart, columnCount and relationTargets.
The data model response in detail mode
With table, each repository entry has mode: "detail" and returns the
matching tables in full plus the matching enums. matchedBy is exact when a
table's name, schema.name or declaring class equals your query, substring
when none did and every table whose name or class contains it is returned, and
none when nothing matched. totalMatches is the count before the cap. Detail
mode returns at most 20 tables and 50 enums per repository, with truncated
set when either cap was hit.
A full table has name, nameSource (explicit or convention), schema, entityName, orm, kind, filePath,
lineStart, lineEnd, primaryKey, columns, indexes and relations.
columns[]object[]name, propertyName, rawType (as written), type (normalized to string, integer, float, decimal, boolean, datetime, date, time, json, uuid, binary, enum, array or unknown), nullable (null when unknowable), primaryKey, unique, defaultValue, enumValues, line.relations[]object[]kind (one_to_one, one_to_many, many_to_one or many_to_many), propertyName, targetEntity, targetTable, columns, referencedColumns, throughTable, onDelete, line.indexes[]object[]name, columns, unique.Each detail entry also has enums: name, values, orm, filePath, line, for enums whose name contains your query or that a returned table's enum columns reference.
The repository fields on every data model entry
Every entry, in either mode, starts with repositoryId, repoFullName,
commitSha, extractedAt, orms, tableCount, enumCount and
warningCount. commitSha is the commit the model was extracted at: compare it
to your own before you trust it. extractedAt: null means no model has been
extracted for that repository yet, and its counts are zero.
Errors from the data model route
| Status | error | When |
|---|---|---|
400 | VALIDATION_ERROR | repositoryId is not a UUID, or table is over 200 characters. |
401 | TOKEN_INVALID | The credential is missing, unknown, revoked or expired. |
403 | INSUFFICIENT_PERMISSIONS | Missing knowledge:read, the plan gate, or no workspace on the token. |
404 | RESOURCE_NOT_FOUND | repositoryId belongs to another workspace or to nothing. |
429 | RATE_LIMITED | Over the soft tier. |
curl -s -G https://api.getnima.io/v1/code-intel/data-model \
-H "Authorization: Bearer $NIMA_API_KEY" \
--data-urlencode "table=invoices"Retrieval channels
The matchedChannels field of a search hit says why a file surfaced. Your query
fans out across several channels at once, the ranks are fused, and a model
reranks the candidates against the question. A strong hit usually carries
several channels.
| Channel | Matched on |
|---|---|
keyword | Full text search over the file's indexed text. |
vector | Embedding similarity between your query and the file's summary. |
vector_hyde | The same embedding index, queried with a hypothetical answer generated from your question. |
symbol | Full text search over the names of symbols declared in the file. |
filepath | The path contains an identifier pulled out of your query. |
activity | Commit messages and pull request text matched by embedding, mapped back to the files they touched. |
graph | Reached by following import and reference edges out from the top ranked files. |
What the code index holds
The code index holds one row per file for 22 file extensions of source code, plus a fixed set of documentation files. Nothing else in a repository is searchable.
| Indexed | Detail |
|---|---|
| Code | .ts .tsx .js .jsx .mjs .cjs .py .go .java .swift .php .cs .rs .c .h .cpp .cc .cxx .hpp .hh .hxx .sol. These are also the files parsed for symbols. |
| Documentation | README.md, ARCHITECTURE.md, CONTRIBUTING.md, CHANGELOG.md, CLAUDE.md, AGENTS.md, GEMINI.md in any directory except top-level .claude, .github, .husky, .vscode, research, worked, terraform and infrastructure; MANIFEST.md and ROADMAP.md at the root; Markdown files under docs/; and files under .cursor/rules/. These carry no symbols. |
| Never indexed | Anything under node_modules, dist, build, .git, .next, coverage, vendor, __pycache__ or .turbo. |
A file type outside that list, such as .rb or .kt, can never appear in
results, however well it matches. Summaries are written when the file is
indexed, from the first 300 lines of a code file or the first 2,000 lines of a
documentation file, so a longer file has a summary describing only its opening,
and a file changed since the indexed commit has a summary of the old version.
Only the default branch is indexed. Read
Code intelligence for how indexing runs.
Code routes not reachable with a key
Five code routes exist for a signed-in browser session and refuse every API key
and OAuth token. A route that declares no scope answers 403 with This endpoint does not accept OAuth access tokens, whichever scopes you hold, and
the admin panel has no control for any of them.
| Method and path | What it does |
|---|---|
GET /v1/code-intel/repositories | Lists indexed repositories with a file count. |
GET /v1/code-intel/repositories/{id}/stats | The last 10 index runs for one repository, with phases, costs and warnings. |
GET /v1/code-intel/repos/{repoId}/search | An older single repository search that returns hits with no index metadata. |
GET /v1/code-intel/repos/{repoId}/traces/{conversationId} | The retrieval trace of one assistant conversation about that repository. |
POST /v1/code-intel/repositories/{id}/reindex | Triggers a full re-index, at most once per repository per 24 hours; a second call inside the window answers 429. Needs a signed-in session of a workspace owner or admin. |
The repositories array on a search response is the only way to read index
state with a key: it carries the id, status, indexed commit, last sync time and
warnings for every repository. To refresh an index, push to the default branch
and let the index follow, or ask the nima team. There is no route for reading a
file's contents.