Skip to main content

Search Filters

Filters let you narrow results by document metadata, classifier categories, and custom fields you upload.

Search and document listing hide soft-deleted chunks by default. Use include_deleted on the public APIs when you intentionally need to inspect deleted content.

Search filters (POST /search)

Filters work as payload conditions: scalar values match exactly, and string or integer lists match any value in the list.

{
"query": "capital city",
"filters": {
"document_id": "malaysia-wiki-001",
"classification.categories": ["sustainability", "other"]
}
}

List values use OR logic within a single field. This example matches either sustainability OR other classifier categories while still requiring any other filter keys you include. Short keys such as categories, author, or title target your custom metadata; system metadata uses documented dotted paths such as source.filename and classification.status (the full metadata.* forms also work).

Including soft-deleted chunks

Soft-deleted chunks are excluded from normal search results with a live-only system filter. Set include_deleted to true when you are auditing deleted data or verifying a 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",
"include_deleted": true
}'

include_deleted only changes the deletion visibility rule. The request is still scoped to the client resolved from X-API-Key.

Custom metadata filters

Any field uploaded via metadata during /embed or /update can be used as a filter. These are stored under metadata.custom.*:

Automatic Custom Metadata Indexing

Custom metadata fields are automatically indexed at system startup. The system scans existing documents and creates Qdrant payload keyword indexes for every custom key it finds. You do not need to manually configure indexes for any field you supply — it is handled automatically. Even custom metadata on documents indexed before the auto-indexing was added still works for filtering; Qdrant falls back to a full scan for unindexed fields, which is fine for moderate collection sizes.

# 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 "Content-Type: application/json" \
-d '{
"query": "python developer",
"filters": {
"department": "Engineering",
"team": "Backend"
}
}'

The filter keys department and team are automatically resolved to metadata.custom.department and metadata.custom.team.

Output excerpt from the 2026-06-11 run with metadata-entities-001:

{
"meta": {
"query": "Kuala Lumpur",
"total": 10,
"offset": 0,
"limit": 5,
"has_more": true,
"query_time_ms": 18,
"retrieval": {
"alpha": 0.7,
"reranked": false
},
"applied_filters": {
"department": "Engineering",
"team": "Backend"
}
}
}

Use list values when a search should cover multiple values for the same metadata field:

curl -X POST http://localhost:8000/search \
-H "X-API-Key: super-secret-key" \
-H "Content-Type: application/json" \
-d '{
"query": "budget report",
"filters": {
"tenant_id": "tenant-a",
"department": ["Finance", "Operations"]
}
}'

This searches within tenant-a across both Finance and Operations.

Client isolation and reserved filter keys

Results are automatically scoped to the client that owns your API key. You never need to — and cannot — filter by client. The client_id field is a reserved top-level payload key (sibling of document_id, not stored under metadata.*). Soft-delete marker fields (deleted, deleted_at, and deleted_by) are also reserved top-level payload keys. Attempting to use any reserved key as a filter returns a 400 error:

{"detail":"Reserved filter keys are not allowed: client_id"}
{"detail":"Reserved filter keys are not allowed: deleted"}

This applies to top-level keys and metadata.custom.* aliases. For example, metadata.custom.deleted is rejected instead of being treated as user metadata. Use include_deleted=true for read visibility and POST /document/:document_id/restore to clear deletion markers.

System metadata filters

Filter by system-generated metadata using the documented dotted paths. You can also prefix these keys with metadata. if you want to use the full Qdrant payload path:

curl -X POST http://localhost:8000/search \
-H "X-API-Key: super-secret-key" \
-H "Content-Type: application/json" \
-d '{
"query": "quarterly report",
"filters": {
"source.filename": "report.pdf",
"location.page_number": 1
}
}'

Available system metadata paths:

PathFull pathTypeExampleDescription
source.filenamemetadata.source.filenamestring"report.pdf"Original source filename
source.mime_typemetadata.source.mime_typestring"application/pdf"Original MIME type
source.extensionmetadata.source.extensionstring"pdf"Original extension
location.page_numbermetadata.location.page_numberinteger1Page number within document
location.page_labelmetadata.location.page_labelstring"1"Page label (may differ from number)
location.chunk_indexmetadata.location.chunk_indexinteger0Chunk index within document
document.page_countmetadata.document.page_countinteger10Total pages in document
classification.categoriesmetadata.classification.categoriesstring or list"risk management"Assigned categories
classification.statusmetadata.classification.statusstring"completed"Classification status
quality.text_extractionmetadata.quality.text_extractionstring"native" or "ocr"Extraction method
quality.ocr_usedmetadata.quality.ocr_usedbooleantrue or falseWhether OCR was used
extracted.titlemetadata.extracted.titlestring"Annual Report"Extracted title
extracted.authormetadata.extracted.authorstring"Jane Doe"Extracted author
extracted.created_atmetadata.extracted.created_atstring"2026-04-09"Extracted creation date
metadata.custom.*metadata.custom.*anyuser-definedCustom uploaded metadata

Entity filters

You can also filter by named entities extracted during document processing. Entity filters support both single string values and list values.

Single string value

Match chunks that contain a specific entity:

curl -X POST http://localhost:8000/search \
-H "X-API-Key: super-secret-key" \
-H "Content-Type: application/json" \
-d '{
"query": "quarterly earnings",
"filters": {
"persons": "John Doe"
}
}'

List value (OR semantics)

Pass a list to match chunks that contain any of the specified entities:

curl -X POST http://localhost:8000/search \
-H "X-API-Key: super-secret-key" \
-H "Content-Type: application/json" \
-d '{
"query": "loan application",
"filters": {
"organizations": ["Bank A", "Bank B"]
}
}'

This returns chunks where organizations contains either "Bank A" OR "Bank B".

AND across filter keys

When you combine multiple entity filter keys, they use AND logic. A chunk must satisfy every condition to be returned:

curl -X POST http://localhost:8000/search \
-H "X-API-Key: super-secret-key" \
-H "Content-Type: application/json" \
-d '{
"query": "quarterly earnings",
"filters": {
"persons": "John Doe",
"organizations": ["Bank A", "Bank B"]
}
}'

This matches chunks where persons contains "John Doe" AND organizations contains either "Bank A" OR "Bank B".

Available entity types

Filter KeyExample ValueDescription
persons"John Doe" or ["John Doe", "Jane Smith"]People mentioned in the document
organizations"Bank of America" or ["Bank A", "Bank B"]Companies, banks, institutions
dates"2024-01-15" or ["2024-01-15", "2024-03-20"]Dates and time expressions
locations"New York" or ["New York", "London"]Geographic locations
monetary_amounts"$1,000,000" or ["$1,000,000", "€500,000"]Currency and monetary values
account_numbers"1234567890" or ["1234567890", "0987654321"]Account numbers
transaction_refs"TXN-12345" or ["TXN-12345", "REF-67890"]Transaction reference numbers
account_types"savings" or ["savings", "checking"]Types of accounts

Entity filters can be combined with metadata and custom filters in the same request.

Combined example

curl -X POST http://localhost:8000/search \
-H "X-API-Key: super-secret-key" \
-H "Content-Type: application/json" \
-d '{
"query": "quarterly earnings",
"filters": {
"department": "Engineering",
"project": "alpha",
"persons": "John Doe",
"organizations": ["Acme Corp", "Globex"]
}
}'

This returns only chunks that satisfy all conditions simultaneously:

  • Custom metadata department is "Engineering"
  • Custom metadata project is "alpha"
  • persons contains "John Doe"
  • organizations contains "Acme Corp" OR "Globex"

Document listing filters (GET /documents)

curl "http://localhost:8000/documents?metadata_filter={\"categories\":\"other\"}" \
-H "X-API-Key: super-secret-key"
note

Document listing filters are separate from /search filters. GET /documents currently supports scalar metadata equality filters only. Use POST /search when you need list values such as department: ["Engineering", "Data"].

Document listings also hide soft-deleted documents by default. Add include_deleted=true to include them and return deleted, deleted_at, and deleted_by fields:

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 marker fields are reserved system state, not user metadata.