Searching¶
Find live files and folders by name, current text, tags, or modification time. Search returns at most 50 results by default and tells you when more match.
docbank search insurance
docbank search tax 2026
docbank search return --tag taxes
docbank search report --mime-type application/pdf
docbank search receipt --under /taxes/2026
docbank search report --modified-since 2026-01-01T00:00:00Z
docbank search report --modified-before 2026-04-01T00:00:00Z
docbank search --modified-since 2026-01-01T00:00:00Z --modified-before 2026-04-01T00:00:00Z
docbank search --tag taxes
docbank search report --limit 200
docbank search report --json
SELECTOR MATCH PATH
id:231 name /taxes/2026/insurance-renewal.pdf
id:198 content /taxes/2026/car-insurance-notes.md
QueryV1 and saved queries also support photo quality filters.
For example, advanced search brightness_min:0.4 color_blue_min:0.5 selects
bright, blue-leaning photos, while unevaluated:true selects photos with
missing measurements.
How do words match?¶
Each whitespace-separated term matches the start of a word. For example,
insur finds insurance-renewal.pdf. Every term must match.
Docbank searches query text literally. It escapes AND, OR, quotes, and
parentheses before passing the query to SQLite's FTS5 search index. You do not
need to escape them yourself.
Name matches appear before content-only matches. Within each group, Docbank
uses BM25, a text-relevance score, then name and ID to break ties consistently.
The MATCH column tells you which group produced the result.
Which documents can match?¶
Search includes live files and directories, excluding the vault root. Trashed nodes do not appear. Restoring them returns them to search. Renames update the name index immediately.
Text search uses only the current version of each live file. Ordinary search
does not cover older versions. Read those with docbank versions.
How do I narrow the results?¶
| Filter | What it selects |
|---|---|
--tag <name-or-id> |
Nodes currently assigned to one tag. |
--mime-type <type/subtype> |
Files whose current version has that media type. |
--under <path-or-id> |
Descendants of one live directory. |
--modified-since <timestamp> |
Nodes modified at or after the timestamp. |
--modified-before <timestamp> |
Nodes modified before the timestamp. |
The CLI resolves tag names to stable UUIDs before searching. JSON echoes the
selected UUID as tag_id. A later tag rename cannot change the tag selected by
that request, and filtering does not change name-before-content ranking.
Media-type matching ignores case and stored parameters. For example,
text/plain also matches text/plain; charset=utf-8. The filter itself must
contain no parameters. Directories and historical versions never match it.
The directory filter accepts a path or id:N. The CLI resolves it to a stable
node ID, which JSON echoes as under_node_id. Moving or renaming that
directory does not change which directory the request selects. The directory
itself is excluded from results.
Time filters require absolute RFC3339 timestamps. Docbank normalizes
offsets to UTC and echoes the bounds in JSON. The start is inclusive and the
end is exclusive, so adjacent ranges do not duplicate a boundary result.
These filters use the live node's current modified_at, not the source file's
modification time or the age of an older version.
Can I search with filters alone?¶
Yes, when you provide --tag, --modified-since, or --modified-before.
These results use newest modified_at first and show filter in the MATCH
column. The usual result limit still applies.
A blank or whitespace-only query with only --mime-type or --under is
rejected. Add text, a tag, or a time bound before using those filters.
How do I handle incomplete results?¶
--limit accepts 1–1000. Docbank always reports when the result was truncated.
For scripts, --json returns hits, limit, and truncated. hits is always
an array, including when nothing matches. The limit caps response size, not
work inside the database.
Search has no continuation cursor. When truncated is true, increase the
limit or narrow the query. Time ranges alone cannot recover every result when
more than 1,000 nodes share one modification timestamp, such as after a large
restore.
Find documents with identical content¶
Use authenticated GET /api/v1/duplicates?limit=50&offset=0 to find live
documents whose current versions share a SHA-256 content identity. A group
requires at least two documents. Shared bytes do not mean the archive entries
are interchangeable: each keeps its own node, version, path, and import
memberships. This read does not delete or merge anything.
The response reports exact group and current-reference totals. Groups sort by
SHA-256. limit accepts 1–100 groups and defaults to 50. Increase offset to
read the next page. Each page reflects one read snapshot, not a frozen result
set across requests, so concurrent changes can affect later pages.
Each group previews at most 16 references and reports references_truncated
when more exist. References sort by earliest current modification time, then
node ID, and representative_node_id identifies the first. This choice does not
assert which document was originally authored first. Each preview also shows
up to 16 eligible collection identities and their labels, with an exact
collection_count and collections_truncated flag.
Historical versions and trash do not inflate live duplicate groups. To inspect
all retained references for one hash, use GET /api/v1/content-references with
sha256, limit, and offset. That lookup distinguishes current versions,
live history, and trash. Duplicate discovery is a separate HTTP read. It does
not change text-search results or automatically collapse them.
Export search counts¶
Use Search exports to compare date-scoped search terms in an eight-column CSV and keep a frozen evidence ZIP. Recent export history keeps reusable requests. Named saved queries are a separate workflow.
Photo query predicates¶
Saved QueryV1 definitions can include photo kind, case-insensitive complete
camera/lens make or model, safe integer ISO bounds, capture dates,
decimal-string GPS boxes, asset UUID sets, and album UUID sets through set: or filters.set_ids. See
Browse photo assets over HTTP for the
complete field and sort contract. Existing tag, text, and collection predicates
combine through the same compiler.
Photos evaluates photo metadata predicates against the selected display file.
Ordinary file predicates can still match individual members. Document snapshots
use each document's own metadata. They support these predicates, but
capture_time and import_time sorting is available only in Photos. Album browsing also accepts added_time with an explicit album scope. A document snapshot request naming any of these fields returns an error.
Save complete query intent over HTTP¶
Save a named search definition when several clients need to reuse it. The saved-definition HTTP API stores the intent with the vault's metadata. A separate run endpoint executes a saved query. Saved definitions and run receipts survive backup and restore, but snapshot rows do not.
The web management screen also manages saved definitions.
A query payload uses QueryV1: a JSON object with version v: 1, search text,
filters, and optional syntax, mode, and sort choices. A saved mode such as
hybrid describes intent. Query snapshots reject it.
Processing search requires a named processing profile and a
source-version fence.
See the payload reference
for accepted fields and limits.
The examples below use DOCBANK_URL and DOCBANK_API_KEY from the
agent connection setup.
Create a query with a unique name:
curl --fail-with-body --silent --show-error -i \
-H "X-Api-Key: $DOCBANK_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"name":"Quarterly reports","kind":"query","payload":{"v":1,"text":"quarterly report","filters":{"paths":["/reports"]}}}' \
"$DOCBANK_URL/api/v1/saved-queries"
Keep the returned id and ETag. The ETag is a quoted revision number, such
as "1". Use the ID to read the definition:
curl --fail-with-body -i \
-H "X-Api-Key: $DOCBANK_API_KEY" \
"$DOCBANK_URL/api/v1/saved-queries/<saved-query-id>"
To edit it, send the ETag from your last read as If-Match. Replace the
example ID and revision with those returned by your daemon:
curl --fail-with-body -X PATCH \
-H "X-Api-Key: $DOCBANK_API_KEY" \
-H 'If-Match: "1"' \
-H 'Content-Type: application/json' \
--data '{"description":"Reports for quarterly review"}' \
"$DOCBANK_URL/api/v1/saved-queries/<saved-query-id>"
Only name, description, and payload can change. A supplied payload
replaces the complete payload. A stale revision returns 412 stale_revision.
Read again and reconsider the edit. To delete a definition, send DELETE to
the same URL with its current If-Match.
List definitions with GET /api/v1/saved-queries?kind=query&limit=100&offset=0.
The response includes items, total, limit, and offset. Omit kind to
list both kinds of definition. Each definition has a SHA-256 fingerprint of
its normalized payload, a revision, and UTC timestamps.
How do I save a highlight set?¶
A highlight set is an ordered list of literal text and colors. Save one through
the same endpoint with kind: "highlight_set":
curl --fail-with-body -i \
-H "X-Api-Key: $DOCBANK_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"name":"Review terms","kind":"highlight_set","payload":{"v":1,"terms":[{"text":"invoice","color":"#ffff00"},{"text":"due date","color":"#aaffaa"}]}}' \
"$DOCBANK_URL/api/v1/saved-queries"
Use 1–64 unique terms, each 1–256 Unicode characters long. Colors must use
lowercase #rrggbb. Terms are literal text, not regular expressions. Order is
preserved. Read, edit, and delete the set by its returned ID, using the same
revision rules as a query.
What are the saved-definition limits?¶
The web application manages saved definitions. The CLI and TUI have no matching management command or screen. Creating, reading, updating, or deleting a definition does not execute saved queries, render highlights, or return result counts. Use the saved-query run endpoint for execution.
Once permanent audit history is enabled anywhere in the vault, create, update,
and delete return 409 audit_mutation_unsupported. Listing and reading still
work. See Permanent audited history before enabling it
in a vault that needs editable saved definitions.
Run an exact query over HTTP¶
Create a daemon-lifetime snapshot when you need the complete QueryV1 contract, exact totals, and stable pages while the live vault keeps changing:
curl --fail-with-body --silent --show-error \
-H "X-Api-Key: $DOCBANK_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"query":{"v":1,"syntax":"advanced","text":"name:(budget OR forecast) AND NOT extension:tmp","filters":{"paths":["/projects/example"]},"sort":{"field":"name","direction":"asc"}},"page_size":50,"facets":["extension","tags"]}' \
"$DOCBANK_URL/api/v1/workspace/queries"
The response contains the first rows, exact total and total_bytes, and a
snapshot_id. When next_cursor is present, send it back unchanged with the
returned snapshot ID:
curl --fail-with-body --silent --show-error \
-H "X-Api-Key: $DOCBANK_API_KEY" \
-H 'Content-Type: application/json' \
--data '{"cursor":"<next-cursor>"}' \
"$DOCBANK_URL/api/v1/workspace/queries/<snapshot-id>/pages"
Do not rebuild a cursor or add query, filter, facet, or page-size overrides to
a page request. A missing next_cursor means there is no next page. Rows,
ordering, totals, and original node/content versions remain fixed even if the
vault changes after creation.
To execute a saved query, first read its current definition and keep its ETag. Then run that revision. The request body can change execution options but cannot replace the saved QueryV1 payload:
curl --fail-with-body --silent --show-error \
-H "X-Api-Key: $DOCBANK_API_KEY" \
-H 'If-Match: "<saved-query-revision>"' \
-H 'Content-Type: application/json' \
--data '{"page_size":100,"facets":["collections","text_coverage"]}' \
"$DOCBANK_URL/api/v1/saved-queries/<saved-query-id>/runs"
The {run,snapshot} response includes a durable receipt comparing this run
with the previous one and the first ephemeral snapshot page. A receipt proves
what ran and its exact totals. It cannot restore the rows. If paging returns
410 snapshot_gone, create a new workspace snapshot or run the saved query
again, then use only the new snapshot's cursors.
Snapshot execution supports duplicate and text-coverage constraints. Supply a
configured profile when the query uses text coverage. Semantic and hybrid
modes and relevance ordering remain unsupported. The web query editor runs a
validated draft through this workspace endpoint and renders its exact totals,
facets, frozen authority, and forward/backward pages. See
Work with a frozen query.
Preview a field-aware query¶
In the web application, choose Edit query for expression editing, structured facet summaries, syntax help, and positioned server errors. A saved query can open in the same editor, and an import collection can start a new collection-scoped draft. Text, facets, mode, and sort remain together when saved or kept in the URL. Closing the editor does not discard the draft.
Collection quality can also open a scoped draft from selected distribution
values. For example, selecting pdf and txt extensions creates
(extension:"pdf" OR extension:"txt") with the collection identity kept
as a structured filter. Suggestions require an explicit action and leave live
search unchanged.
The editor never sends an unsupported query through ordinary live search with constraints removed. After validation, Run query creates a new frozen snapshot from the complete draft. Draft edits do not alter the accepted snapshot until another run succeeds. Closing the editor keeps the draft in the current session. Save a named definition before reloading or leaving it.
POST /api/v1/queries/parse validates a QueryV1 expression and resolves its
references. It returns query, query_fingerprint, and dependencies, each
with a kind, stable id, and observed revision. It does not return search
results, change saved definitions, or expose SQL. The CLI and /search keep
the simple search behavior described above.
For example, submit this JSON to preview a name expression with a separate size filter:
{
"text": "name:(budget OR forecast) AND NOT extension:tmp",
"syntax": "advanced",
"filters": {"size_min": 100}
}
The response keeps the complete entered text. Expression fields are not removed from it or copied into hidden filters.
Advanced syntax supports exact terms, quoted phrases, a trailing * for
prefix matching, parentheses, and uppercase AND, OR, and NOT. Whitespace
between operands means AND. Precedence is NOT, then NEAR, then AND,
then OR. A backslash escapes the next character: note\:draft is literal
text, and \AND is not an operator. Simple syntax treats these operators as
literal prefix terms, as the CLI does.
alpha NEAR/5 beta requires the two terms or phrases to occur within five
tokens in the same indexed source. Bare NEAR uses ten. Distances range from
zero to 1,000. Chained NEAR expressions and Boolean operands inside NEAR are
rejected. name:(alpha NEAR/0 beta) restricts the match to the filename.
| Field | Meaning |
|---|---|
name |
Filename text, including phrases and prefixes |
path |
Exact case-sensitive virtual path or its descendants; quote paths containing spaces |
tag, collection, saved |
Exact name or stable UUID; grouped Boolean choices are supported, prefixes and NEAR are not |
mime, extension, media_family |
Current MIME essence, filename extension, or shared media family |
modified_after, modified_before |
Inclusive lower and exclusive upper RFC3339 time bounds; quote timestamps |
size_min, size_max |
Inclusive nonnegative byte bounds |
Reference names are case-sensitive and Unicode-normalized. A canonical UUID
selects an ID, without falling back to a name. Unknown references fail even
under NOT. A saved reference expands as a grouped expression with its own
filters, so name:budget OR saved:Review does not apply Review's filters to
the name branch. Nested field overrides such as name:(tag:urgent) are not
supported.
Structured filter dimensions combine with AND. Values within one include
list combine with OR, and an exclude list removes that union. Paths are
subtree constraints, not wildcard patterns. Collection unions do not duplicate
files. Time comparisons keep nanosecond precision.
Preview and snapshot execution support duplicate and text-coverage constraints. Snapshot execution requires a configured processing profile for coverage predicates. Semantic/hybrid mode and relevance ordering return errors instead of being ignored.
Expression errors return 422 invalid_query with position.offset and
position.end: a half-open UTF-8 byte span in the submitted text. Database
failures remain server errors. Preview limits include 8,192 text code points,
512 parsed nodes, 32 levels of syntactic nesting, and 16 nested saved
references. Larger expansions and compiled predicates have additional bounds.
Dependency revisions describe this preview, not a permanent result snapshot.
Text extraction¶
The daemon's extract:plain-text background job indexes UTF-8 content whose
media type is text/*, application/json, application/x-ndjson, or
application/jsonl. This covers plain text, Markdown, CSV, JSON, and JSONL.
The worker reads the stored content and checks its hash, whether Docbank stores
it as an individual file or inside a pack. Text becomes searchable only after
that complete read passes verification.
A separate extract:email job handles files imported as
message/rfc822. It preserves the raw message and canonical MIME inventory,
then indexes only the selected outer body. Alternate bodies, nested forwarded
messages, headers, and attachments do not leak into ordinary search. HTML is
converted to bounded visible text before publication. An empty, unsupported,
encrypted, or over-limit body is recorded in metadata but contributes no
search hit.
.eml, .EML, and mixed-case variants are declared as message/rfc822 at
new import, regardless of the host MIME registry. Existing content with
another stored media type is never reclassified from its filename. An embedded
or master-API caller may explicitly ensure its email metadata without changing
the original media type, hash, or bytes.
Applications can publish email attachments as ordinary documents. Each attachment keeps a relation to its parent version and MIME part. Published CSV and other supported text files enter normal text extraction. Other formats require a configured processing provider and consent. Attachment text matches the child document, not the parent email's body.
Extraction is bounded to 16 MiB per blob. Larger documents, invalid UTF-8, and
text containing NUL bytes remain stored and readable but are not body-indexed.
Newly ingested or replaced content may take a few seconds to appear while the
daemon job reaches it. A transient open, read, or verification error leaves the
item queued and is retried on a bounded delay. It does not become a permanent
extraction failure. docbank jobs shows whether that worker is running.
Which text is not searched?¶
The daemon does not automatically extract PDF text layers, office-document text, unpublished email attachments, or text from images through optical character recognition (OCR). You can still find these files by name and read their stored bytes. The document processing libraries provide additional processing options for applications. Adding a file does not start them.
Ordinary search in the CLI, HTTP API, web app, and TUI matches words in names and indexed text. Processing search uses the separate workflow below. Saving search choices in QueryV1 does not execute either workflow.
Processing search¶
Configured document processing adds a separate search over renditions and embeddings, limited to the source versions the caller names. It requires a mode, a profile, and an authorized set of source versions. It does not change ordinary name and plain-text search. See Document processing search for modes, coverage, and the consumer contract.
Next: organize documents beyond paths with Organizing and tagging, or see every search flag in the CLI reference.