Search Filters
POST /search returns the Evidence Result contract. Each row represents one Document Version
with canonical metadata, lane-local retrieval contributions, and bounded Content and Metadata
Evidence Sets. Pagination and meta.total count Document Versions, not chunks or Metadata
Leaves.
For ordinary text retrieval, send explicit "scope": "content". scope is case-insensitive;
the canonical values used in requests and downstream processing are lowercase: content,
metadata, and both.
Exact filters are not ranked Metadata search. The
filtersobject only narrows the candidate set and never changes retrieval rank. Ranked Metadata search uses"scope": "metadata"or"scope": "both"and is documented in Searching Metadata. This guide documents exact filter constraints only.
Filters narrow the candidate set by document metadata, classifier categories, custom fields uploaded with a document, and entities extracted during processing. They are exact constraints: they do not affect result rank.
Search filters (POST /search)
Scalar values match exactly. A list of strings or integers uses OR logic within that key. Keys are combined with AND logic:
{
"query": "capital city",
"scope": "content",
"filters": {
"document_id": "malaysia-wiki-001",
"classification.categories": ["sustainability", "other"]
}
}
This requires the document_id constraint and either listed classifier category. Filters do
not change the rank or retrieval contributions of a returned Evidence Result.
Including soft-deleted evidence
Soft-deleted Document Versions and their evidence are hidden by default. Set include_deleted
to true only for an intentional audit or restore workflow:
curl -X POST http://localhost:8000/search \
-H "X-API-Key: super-secret-key" \
-H "Content-Type: application/json" \
-d '{
"query": "capital city",
"scope": "content",
"include_deleted": true
}'
include_deleted changes lifecycle visibility only. The request remains scoped to the Client
resolved from X-API-Key, the active generation, and publication_eligible versions. It never
bypasses Client isolation or publication authority checks.
Custom metadata filters
Any custom metadata uploaded through /embed or /update can be used as an exact filter.
Custom metadata keys must use the explicit metadata.custom. prefix in filters. Top-level
fields, entity keys, and documented system shorthands are the only keys accepted bare:
| Request key | Resolved payload key |
|---|---|
metadata.custom.department | metadata.custom.department |
source.filename | metadata.source.filename |
metadata.source.filename | metadata.source.filename |
document_id | document_id |
version_id | version_id |
created_at | created_at |
An unqualified key that is not a top-level field (document_id, version_id,
created_at), entity key (e.g. persons, organizations), or documented system shorthand
(e.g. source.filename, classification.status) is rejected with 400. Dotted identifier
keys remain nested paths; keys with spaces or other unsupported path characters are quoted as
one custom key.
# Upload with custom metadata
curl -X POST http://localhost:8000/embed \
-H "X-API-Key: super-secret-key" \
-F "file=@resume.pdf" \
-F "document_id=candidate-001" \
-F 'metadata={"department":"Engineering","team":"Backend","author":["wanjia","adlin"]}'
# Filter by custom metadata
curl -X POST http://localhost:8000/search \
-H "X-API-Key: super-secret-key" \
-H "Content-Type: application/json" \
-d '{
"query": "python developer",
"scope": "content",
"filters": {
"metadata.custom.department": "Engineering",
"metadata.custom.team": "Backend"
}
}'
Custom metadata filter keys use the explicit metadata.custom. prefix. Lists match any value
within that key while other keys remain AND constrained:
curl -X POST http://localhost:8000/search \
-H "X-API-Key: super-secret-key" \
-H "Content-Type: application/json" \
-d '{
"query": "budget report",
"scope": "content",
"filters": {
"metadata.custom.tenant_id": "tenant-a",
"metadata.custom.department": ["Finance", "Operations"]
}
}'
This searches within tenant-a across both Finance and Operations without changing
Evidence Result rank.
Keys with spaces (or other special characters)
Custom metadata keys with spaces must use the explicit metadata.custom. prefix. Quote the
key in the JSON filter. For a body-based POST /search filter, the JSON key is used directly.
For GET /documents, URL-encode the entire JSON value in metadata_filter:
curl -sS --get "http://localhost:8000/documents" \
-H "X-API-Key: super-secret-key" \
--data-urlencode 'metadata_filter={"metadata.custom.Field A":"Acme"}' \
| jq '{documents: [.documents[] | {document_id, metadata: {custom: .metadata.custom}}], total, has_more}'
The trimmed response contains only the document whose exact Field A value is Acme:
{
"documents": [
{
"document_id": "spaced-key-demo.txt",
"metadata": {"custom": {"Field A": "Acme"}}
}
],
"total": 1,
"has_more": false
}
Client isolation and reserved filter keys
Results are automatically scoped to the Client that owns the API key. Callers never provide
client_id and cannot filter by it. The client_id key is reserved top-level system state,
as are the Soft Delete markers deleted, deleted_at, and deleted_by. Attempting to use a
reserved key returns 400:
{"detail":"Reserved filter keys are not allowed: client_id"}
{"detail":"Reserved filter keys are not allowed: deleted"}
This also applies to metadata.custom.* aliases. Both metadata.custom.client_id and
metadata.custom.deleted are rejected rather than treated as user metadata. Use
include_deleted=true for read visibility and POST /document/{document_id}/restore to clear
deletion markers.
Unsupported metadata.* paths, legacy metadata.parsing.*, metadata.processing.*, and
metadata.ocr.* paths, and unsupported entities.* paths are rejected with 400; they do
not fall back to custom metadata. A bare key that is not a top-level field, entity key, or
documented system shorthand is also rejected with 400; custom metadata requires the explicit
metadata.custom. prefix. This fail-closed behavior prevents a misspelled or bare custom key
from silently matching nothing or the wrong field.
System metadata filters
Filter system-generated metadata with the documented dotted paths. The full metadata.* path
also works:
curl -X POST http://localhost:8000/search \
-H "X-API-Key: super-secret-key" \
-H "Content-Type: application/json" \
-d '{
"query": "quarterly report",
"scope": "content",
"filters": {
"source.filename": "report.pdf",
"location.page_number": 1
}
}'
Available system metadata paths:
| Path | Full path | Type | Example | Description |
|---|---|---|---|---|
source.filename | metadata.source.filename | string | "report.pdf" | Source filename. Set from the upload name at embed time; rename later with PATCH /update filename. |
source.mime_type | metadata.source.mime_type | string | "application/pdf" | Original MIME type |
source.extension | metadata.source.extension | string | "pdf" | Original extension |
location.page_number | metadata.location.page_number | integer | 1 | Page number |
location.page_label | metadata.location.page_label | string | "1" | Page label |
location.chunk_index | metadata.location.chunk_index | integer | 0 | Chunk index within the version |
document.page_count | metadata.document.page_count | integer | 10 | Total page count |
classification.categories | metadata.classification.categories | string or list | "risk management" | Assigned categories |
classification.status | metadata.classification.status | string | "completed" | Classification state |
quality.text_extraction | metadata.quality.text_extraction | string | "native" or "ocr" | Extraction method |
quality.ocr_used | metadata.quality.ocr_used | boolean | true or false | Whether OCR was used |
extracted.title | metadata.extracted.title | string | "Annual Report" | Extracted title |
extracted.author | metadata.extracted.author | string | "Jane Doe" | Extracted author |
extracted.created_at | metadata.extracted.created_at | string | "2026-04-09" | Extracted creation date |
metadata.custom.* | metadata.custom.* | any | user-defined | Custom uploaded metadata |
Entity filters
Entity filters target values extracted during document processing. Use one string or a list of strings. A list uses OR semantics within that key; different keys use AND semantics.
Single string value
curl -X POST http://localhost:8000/search \
-H "X-API-Key: super-secret-key" \
-H "Content-Type: application/json" \
-d '{
"query": "quarterly earnings",
"scope": "content",
"filters": {
"persons": "John Doe"
}
}'
List value (OR semantics)
curl -X POST http://localhost:8000/search \
-H "X-API-Key: super-secret-key" \
-H "Content-Type: application/json" \
-d '{
"query": "loan application",
"scope": "content",
"filters": {
"organizations": ["Bank A", "Bank B"]
}
}'
This returns Evidence Results whose Content evidence contains either Bank A or Bank B.
AND across filter keys
curl -X POST http://localhost:8000/search \
-H "X-API-Key: super-secret-key" \
-H "Content-Type: application/json" \
-d '{
"query": "quarterly earnings",
"scope": "content",
"filters": {
"persons": "John Doe",
"organizations": ["Bank A", "Bank B"]
}
}'
This returns Document Versions whose Content evidence satisfies both entity constraints.
| Filter key | Example | Description |
|---|---|---|
persons | "John Doe" | People mentioned in the document |
organizations | "Bank of America" | Companies, banks, and institutions |
dates | "2024-01-15" | Dates and time expressions |
locations | "New York" | Geographic locations |
monetary_amounts | "$1,000,000" | Currency and monetary values |
account_numbers | "1234567890" | Account numbers |
transaction_refs | "TXN-12345" | Transaction references |
account_types | "savings" | Account types |
Entity filters can be combined with custom metadata and system metadata filters in the same request:
curl -X POST http://localhost:8000/search \
-H "X-API-Key: super-secret-key" \
-H "Content-Type: application/json" \
-d '{
"query": "quarterly earnings",
"scope": "content",
"filters": {
"metadata.custom.department": "Engineering",
"metadata.custom.project": "alpha",
"persons": "John Doe",
"organizations": ["Acme Corp", "Globex"]
}
}'
Document listing filters (GET /documents)
Document listing filters are separate from /search filters. GET /documents supports scalar
metadata equality filters; use POST /search for list values. Document listings hide soft-deleted
versions by default; add include_deleted=true to include lifecycle markers:
curl "http://localhost:8000/documents?include_deleted=true" \
-H "X-API-Key: super-secret-key"
Do not filter directly on deleted, deleted_at, or deleted_by; those markers are reserved
system state, not user metadata.