Busabase

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 range

POST /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 questionUseWhat it gives you
Is there already a saved way to do this job?playbooks searchYour 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?searchFuzzy, 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

SourceText used by grep
FilesThe Asset's searchable text slot
DocsThe current Doc body
RecordsThe current approved record fields
PromptsEach 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:

StatusMeaningNext action
missingNo searchable text has been suppliedSupply text or mark the file as having no extractable text
presentCurrent searchable text is availableGrep or read it; replace it when a better extraction is available
staleThe source file changed after text was suppliedSupply text derived from the new file
noneThe file is intentionally marked as having no extractable textSupply 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:

  1. Request a temporary upload URL with POST /api/v1/assets/text/upload-urls.
  2. PUT the UTF-8 .txt bytes to the returned uploadUrl.
  3. Bind the returned storageKey with PUT /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 in SKILL.md therefore says which skill it is in. Folders you cannot open are left out of path. A file uploaded but not yet placed anywhere has no owner.
  • A prompt match names the node the prompt is saved on, the prompt's key, the field that matched (label or body), and the locale of that text (default when the prompt has a single text for every language). To open the prompt as an agent would receive it, pass the key to playbooks get. To find a prompt for a job, use playbooks 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:

OperationChangesSource fileReview
assets.putTextDisposable, derived text used by grep and ranged readsUnchangedDirect and audit-logged
assets.editContentThe canonical bytes of one mounted Drive or Skill text fileChanged after mergeCreates 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

On this page