Search Everything
Supply searchable file text, grep files, Docs, records, and custom prompts, then read only the matching range.
Busabase provides one exact-search loop for people and AI agents:
supply searchable text -> grep -> read the matching line rangePOST /api/v1/grep searches files, Doc bodies, the canonical approved values in Base records, and the custom agent prompts saved on nodes. Results include the source, line, column, and nearby context, so a caller can locate evidence before reading more. A file match also names the skill or drive the file belongs to.
Which search to use
Agents have three ways to find things, and each answers a different question:
| The question | Use | What it gives you |
|---|---|---|
| Is there already a saved way to do this job? | playbooks search | Your skills and custom prompts, ranked, nearest first. Agents call it at the start of every instruction |
| Where exactly does this text appear? | grep (this page) | Regex matches over the full, current data (files, Doc bodies, approved records, custom prompts), with line, column, and coverage |
| What is related, best match first? | search | Fuzzy, ranked, paginated results that also include pending Change Request drafts. A long field value is matched only on its first 8,000 characters, so confirm exact wording with grep |
What is searchable
| Source | Text used by grep |
|---|---|
| Files | The Asset's searchable text slot |
| Docs | The current Doc body |
| Records | The current approved record fields |
| Prompts | Each custom agent prompt's label and body, in every language it is written in |
Text files such as Markdown, JSON, CSV, logs, and source code use their own UTF-8 bytes as searchable text. Binary files such as PDFs, office documents, audio, video, or images need an external tool or agent to extract or transcribe text and supply it to the Asset.
Busabase stores and searches supplied text, but does not run bundled OCR, transcription, or document parsers.
Supply searchable text for a file
An Asset reports one of four textStatus values:
| Status | Meaning | Next action |
|---|---|---|
missing | No searchable text has been supplied | Supply text or mark the file as having no extractable text |
present | Current searchable text is available | Grep or read it; replace it when a better extraction is available |
stale | The source file changed after text was supplied | Supply text derived from the new file |
none | The file is intentionally marked as having no extractable text | Supply text later if OCR or transcription becomes available |
The Asset detail page exposes the same supply, replace, and preview actions. API and agent clients have three write methods.
Small text: write inline
Use putText directly for UTF-8 text up to 1 MB:
curl -X PUT "$BUSABASE_BASE_URL/api/v1/assets/$ASSET_ID/text" \
-H "Authorization: Bearer $BUSABASE_API_KEY" \
-H "Content-Type: application/json" \
--data '{"text":"Extracted contract text\nTermination clause..."}'The response reports the persisted status and counts:
{
"assetId": "asset_contract_pdf",
"textStatus": "present",
"lineCount": 84,
"charCount": 4921,
"byteCount": 4937
}Large text: upload, then bind
For text larger than 1 MB, keep the write streaming-safe:
- Request a temporary upload URL with
POST /api/v1/assets/text/upload-urls. PUTthe UTF-8.txtbytes to the returneduploadUrl.- Bind the returned
storageKeywithPUT /api/v1/assets/{assetId}/text.
SIZE_BYTES=$(wc -c < extracted.txt | tr -d ' ')
curl -X POST "$BUSABASE_BASE_URL/api/v1/assets/text/upload-urls" \
-H "Authorization: Bearer $BUSABASE_API_KEY" \
-H "Content-Type: application/json" \
--data "{\"assetId\":\"$ASSET_ID\",\"sizeBytes\":$SIZE_BYTES}"
# -> { "uploadUrl": "...", "storageKey": "...", "expiresIn": 900 }
curl -X PUT "<uploadUrl>" \
-H "Content-Type: text/plain; charset=utf-8" \
--data-binary @extracted.txt
curl -X PUT "$BUSABASE_BASE_URL/api/v1/assets/$ASSET_ID/text" \
-H "Authorization: Bearer $BUSABASE_API_KEY" \
-H "Content-Type: application/json" \
--data '{"storageKey":"<storageKey>"}'The final bind verifies the uploaded bytes, validates UTF-8, and makes the text searchable. Supplying text again replaces the previous searchable text.
No extractable text: mark none
For an image-only or otherwise unextractable file, mark the text slot explicitly:
curl -X PUT "$BUSABASE_BASE_URL/api/v1/assets/$ASSET_ID/text" \
-H "Authorization: Bearer $BUSABASE_API_KEY" \
-H "Content-Type: application/json" \
--data '{"none":true}'Grep then counts the Asset as unsearchable instead of repeatedly reporting it as missing. You can reverse this later by supplying text.
Grep across files, Docs, records, and prompts
Omit sources to search all four sources, or select the ones you need:
curl -X POST "$BUSABASE_BASE_URL/api/v1/grep" \
-H "Authorization: Bearer $BUSABASE_API_KEY" \
-H "Content-Type: application/json" \
--data '{
"pattern":"termination",
"flags":"i",
"sources":["files","nodes","records","prompts"],
"contextLines":2
}'pattern uses JavaScript regular-expression syntax. A plain word is also a valid pattern. Use scope.files, scope.nodes, or scope.records to narrow a large search.
{
"matches": [
{
"source": "files",
"assetId": "asset_contract_pdf",
"fileName": "contract.pdf",
"drivePath": "legal/contract.pdf",
"owner": { "nodeId": "nod_legal", "nodeType": "drive", "nodeName": "Legal", "path": ["Company"] },
"line": 118,
"column": 1,
"text": "Termination requires 30 days notice.",
"before": ["..."],
"after": ["..."]
},
{
"source": "prompts",
"nodeId": "nod_contracts",
"nodeName": "Contracts",
"nodeType": "base",
"nodeSlug": "contracts",
"key": "check-termination",
"locale": "en",
"field": "body",
"line": 2,
"column": 12,
"text": "Flag any termination clause shorter than 30 days.",
"before": ["..."],
"after": []
}
],
"coverage": {
"files": { "scanned": 24, "missing": [], "stale": [], "unsearchable": 1, "errored": [], "notReached": 0 },
"nodes": { "scanned": 12, "errored": [], "notReached": 0 },
"records": { "scanned": 178, "errored": [], "notReached": 0 },
"prompts": { "scanned": 3, "errored": [], "notReached": 0 }
},
"truncated": false
}What each match tells you:
- A file match carries
owner: the node the file belongs to (nodeType,nodeName) and the folders above it (path). A hit inSKILL.mdtherefore says which skill it is in. Folders you cannot open are left out ofpath. A file uploaded but not yet placed anywhere has noowner. - A prompt match names the node the prompt is saved on, the prompt's
key, thefieldthat matched (labelorbody), and thelocaleof that text (defaultwhen the prompt has a single text for every language). To open the prompt as an agent would receive it, pass thekeytoplaybooks get. To find a prompt for a job, useplaybooks search: grep lists every match, playbooks search ranks them.
maxMatches is shared fairly. Each requested source is guaranteed at least maxMatches ÷ number of sources matches, and whatever a source does not use passes on to the sources after it, in the order files, Docs, records, prompts. One busy source cannot crowd the others out. The whole call also runs against one time limit.
Always inspect coverage before treating no matches as "not found." missing, stale, errored, notReached, or truncated: true means the search was not complete. Missing or stale files need new searchable text. notReached or truncation means the caller should narrow the search rather than raise maxMatches: pick the sources you need, then narrow records with scope.records.baseSlugs or files with scope.files.drivePath.
Read only the matching range
After a file match at line 118, read a bounded window instead of downloading the whole extraction:
curl "$BUSABASE_BASE_URL/api/v1/assets/$ASSET_ID/text/lines?startLine=113&endLine=123" \
-H "Authorization: Bearer $BUSABASE_API_KEY"For a Doc match, use its nodeId with the equivalent Doc endpoint:
curl "$BUSABASE_BASE_URL/api/v1/docs/$NODE_ID/lines?startLine=113&endLine=123" \
-H "Authorization: Bearer $BUSABASE_API_KEY"Asset reads are capped at 2,000 lines and use ranged storage reads, so this grep-then-read pattern remains suitable for very large supplied text.
putText is not editContent
These operations change different things and have different review rules:
| Operation | Changes | Source file | Review |
|---|---|---|---|
assets.putText | Disposable, derived text used by grep and ranged reads | Unchanged | Direct and audit-logged |
assets.editContent | The canonical bytes of one mounted Drive or Skill text file | Changed after merge | Creates a ChangeRequest for human review |
Do not use putText to edit a PDF, document, or mounted text file. Use it only to supply the representation that grep reads. Use editContent when the actual file must change.
There is deliberately no inline asset.text field in Asset responses. Large text remains outside AssetVO; inspect asset.textStatus, write with putText, search with grep, and read bounded ranges with readTextLines.
See also: REST API · MCP Integration · Core Concepts