Skip to main content

Document Management API

Manage uploaded documents through the documents endpoints.

Client Isolation

All document operations are scoped to the client associated with the API key used. Documents are owned per client — two clients may use the same document_id without collision. Operations on one client's documents never affect another client's data.

List Documents

Retrieve a paginated list of all indexed documents belonging to the caller's client.

Endpoint

GET /documents

Request

Headers

HeaderRequiredDescription
X-API-KeyYesValid API key from API_KEY_CLIENTS env var

Query Parameters

ParameterTypeDefaultDescription
limitint100Max documents to return (1-1000)
offsetint0Pagination offset
metadata_filterstringnoneJSON filter for metadata fields
include_deletedbooleanfalseInclude soft-deleted documents and return deletion marker fields

Response

{
"documents": [
{
"document_id": "doc-123",
"metadata": {
"source": {
"filename": "report.pdf"
},
"location": {
"page_number": 1,
"page_label": "1"
},
"document": {
"page_count": 5
},
"classification": {
"categories": ["other"],
"status": "completed"
},
"quality": {
"text_extraction": "native",
"ocr_used": false,
"warnings": []
},
"custom": {
"department": "Engineering"
}
},
"chunk_count": 5,
"created_at": "2024-01-15T10:30:00"
}
],
"total": 42,
"limit": 100,
"offset": 0,
"has_more": true
}

Examples

List All Documents

curl http://localhost:8000/documents \
-H "X-API-Key: super-secret-key"

Paginated Results

# First page
curl "http://localhost:8000/documents?limit=10&offset=0" \
-H "X-API-Key: super-secret-key"

# Second page
curl "http://localhost:8000/documents?limit=10&offset=10" \
-H "X-API-Key: super-secret-key"

Include Soft-Deleted Documents

By default, document listings hide documents that were soft-deleted. Set include_deleted=true to include them and expose the root deletion markers:

curl "http://localhost:8000/documents?include_deleted=true" \
-H "X-API-Key: super-secret-key"

Soft-deleted summaries include these additional fields:

{
"document_id": "doc-123",
"deleted": true,
"deleted_at": "2026-06-29T12:34:56Z",
"deleted_by": "demo-client"
}

Filter by Metadata

Filter Logic: AND Only

All filter conditions are combined with AND logic — every condition must match. There is no OR support. List values are ignored; only single string/boolean/integer values work.

Filter using explicit system metadata paths when you want system-generated values:

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

Filter by source filename:

curl "http://localhost:8000/documents?metadata_filter={\"metadata.source.filename\":\"report.pdf\"}" \
-H "X-API-Key: super-secret-key"

Or with simplified syntax:

curl "http://localhost:8000/documents?metadata_filter=\"metadata.classification.categories\":\"other\"" \
-H "X-API-Key: super-secret-key"

Get Document Text

Retrieve the parsed text already stored in the search index. This endpoint does not retrieve or retain the original uploaded file. Raw dense vectors are withheld by default and returned only when include_vector=true; sparse vectors are never returned.

Chunks are returned in document order with their chunk index and page number when available. Each chunk includes its ID, character count, entities, vector dimension, and server-computed L2 norm. Classification categories and entities are also aggregated across the returned chunks; metadata is returned in the same structured v2 shape as the document APIs.

The top-level embedding object is a curated processing summary for the Console. For newly indexed documents, its dense model, vector dimensions, sparse model, and embedded timestamp come from provenance captured in the document payload and provenance is captured. For older documents without that record, those fields fall back to the current server configuration and provenance is current_config; these fallback values must not be interpreted as the models originally used for that document. The reranker is always the current query-time configuration, and the classifier is always the current classification-only configuration; neither produces the stored search vectors.

When multiple fully versioned copies exist and their created_at values are valid, the endpoint returns the latest version. The response's version_id identifies the selected version; it is null when the returned chunks cannot be attributed safely to one version.

Endpoint

GET /document/:document_id/text

Request

Headers

HeaderRequiredDescription
X-API-KeyYesValid API key from API_KEY_CLIENTS env var

Path Parameters

ParameterDescription
document_idThe document ID whose indexed text to return

Query Parameters

ParameterTypeDefaultDescription
include_deletedbooleanfalseInclude chunks from a soft-deleted document
include_vectorbooleanfalseInclude each chunk's raw dense vector; vectors are withheld by default

Response

{
"document_id": "doc-123",
"version_id": "version-2024-01-15",
"chunk_count": 2,
"created_at": "2024-01-15T10:30:00Z",
"chunks": [
{
"id": "doc-123-chunk-0",
"index": 0,
"page": 1,
"text": "First page of the report.",
"char_count": 25,
"entities": {
"ORG": ["Example Bank"]
},
"vector_dim": 384,
"norm": 1.0,
"vector": null
},
{
"id": "doc-123-chunk-1",
"index": 1,
"page": 2,
"text": "Second page of the report.",
"char_count": 26,
"entities": {},
"vector_dim": 384,
"norm": 1.0,
"vector": null
}
],
"metadata": {
"source": {
"filename": "report.pdf"
},
"custom": {
"department": "Finance"
}
},
"classification": {
"categories": ["financial reporting"],
"status": "completed"
},
"entities": {
"ORG": ["Example Bank"]
},
"embedding": {
"provenance": "captured",
"dense_model": "BAAI/bge-small-en-v1.5",
"dense_dimensions": 384,
"sparse_model": "Qdrant/bm25",
"embedded_at": "2024-01-15T10:30:00Z",
"embedding_version": 1,
"dense_runtime": "fastembed==0.8.0",
"chunker": "sentence-splitter:512:50",
"extraction": "native",
"reranker_model": "Xenova/ms-marco-MiniLM-L-6-v2",
"reranker_scope": "query_time",
"classifier_model": "onnx-community/ModernBERT-base-nli-ONNX",
"classifier_model_file": "onnx/model_quantized.onnx",
"classifier_scope": "classification_only",
"classification_enabled": true
}
}

When the four historical model fields were captured, provenance remains captured even if the added embedding_version, dense_runtime, chunker, and extraction fields are null. When the historical fields are absent, provenance is current_config and the model fields come from the running server configuration. The API does not infer missing provenance detail.

The request is scoped to the client resolved from X-API-Key. An unknown document, a document owned by another client, or a soft-deleted document when include_deleted is false returns 404 Document not found.

Examples

curl http://localhost:8000/document/doc-123/text \
-H "X-API-Key: super-secret-key"

curl "http://localhost:8000/document/doc-123/text?include_deleted=true" \
-H "X-API-Key: super-secret-key"

curl "http://localhost:8000/document/doc-123/text?include_vector=true" \
-H "X-API-Key: super-secret-key"

Delete Document

Delete a document and all its chunks for the authenticated client. Deletes are soft by default: chunks remain in Qdrant with root payload markers and are hidden from normal list/search reads. Use mode=hard only when you need a physical delete.

Endpoint

DELETE /document/:document_id

Request

Headers

HeaderRequiredDescription
X-API-KeyYesValid API key from API_KEY_CLIENTS env var

Path Parameters

ParameterDescription
document_idThe document ID to delete

Query Parameters

ParameterTypeRequiredDescription
version_idstringNoDelete only chunks whose document_id and version_id both match for the authenticated client. Version ids are scoped to document_id, not globally unique. Omit it to delete all versions of the document.
modestringNosoft or hard. Defaults to soft. Hard delete physically removes matching chunks, including chunks already soft-deleted.

Response

{
"document_id": "doc-123",
"deleted_chunks": 5,
"status": "soft_deleted"
}

Examples

Soft-Delete Single Document

curl -X DELETE http://localhost:8000/document/doc-123 \
-H "X-API-Key: super-secret-key"

Hard-Delete Single Document

curl -X DELETE "http://localhost:8000/document/doc-123?mode=hard" \
-H "X-API-Key: super-secret-key"

Hard delete returns status: "hard_deleted", physically removes matching points, and removes /status/{job_id} visibility for jobs indexed to the hard-deleted document or version.

Delete One Document Version

curl -X DELETE "http://localhost:8000/document/doc-123?version_id=v2" \
-H "X-API-Key: super-secret-key"

Chunks store version_id as a top-level indexed payload field. When upload metadata includes version_id, that value is used for the indexed version; otherwise the service uses document_id as the default version id. Version ids are scoped to the authenticated client and document_id, so different documents may reuse the same version_id.

Delete Multiple Documents

for doc_id in doc-001 doc-002 doc-003; do
curl -X DELETE "http://localhost:8000/document/$doc_id" \
-H "X-API-Key: super-secret-key"
done

Delete from Python

import requests

url = "http://localhost:8000/document/doc-123"
headers = {"X-API-Key": "super-secret-key"}

response = requests.delete(url, headers=headers)
print(response.json())
# {'document_id': 'doc-123', 'deleted_chunks': 5, 'status': 'soft_deleted'}

Restore Document

Restore a soft-deleted document by clearing its root deletion markers.

Endpoint

POST /document/:document_id/restore

Request

Headers

HeaderRequiredDescription
X-API-KeyYesValid API key from API_KEY_CLIENTS env var

Path Parameters

ParameterDescription
document_idThe document ID to restore

Query Parameters

ParameterTypeRequiredDescription
version_idstringNoRestore only chunks whose document_id and version_id both match for the authenticated client. Omit it to restore all versions of the document.

Response

{
"document_id": "doc-123",
"restored_chunks": 5,
"status": "restored"
}

Example

curl -X POST http://localhost:8000/document/doc-123/restore \
-H "X-API-Key: super-secret-key"

Update Document Metadata

Update metadata for an existing document without re-uploading it.

Endpoint

PATCH /update

Request

Headers

HeaderRequiredDescription
X-API-KeyYesValid API key from API_KEY_CLIENTS env var
Content-TypeYesapplication/json

Body Parameters

ParameterTypeRequiredDescription
document_idstringYesThe document ID to update
metadataobjectYesMetadata fields to merge into the existing document

Response

{
"status": "updated",
"document_id": "doc-123",
"updated_fields": ["title", "author"]
}

Examples

Update Metadata

curl -X PATCH http://localhost:8000/update \
-H "X-API-Key: super-secret-key" \
-H "Content-Type: application/json" \
-d '{
"document_id": "doc-123",
"metadata": {
"title": "Updated Report",
"author": "Jane Smith"
}
}'

Update from Python

import requests

url = "http://localhost:8000/update"
headers = {
"X-API-Key": "super-secret-key",
"Content-Type": "application/json"
}
data = {
"document_id": "doc-123",
"metadata": {"title": "Updated Report"}
}
response = requests.patch(url, headers=headers, json=data)
print(response.json())
# {'status': 'updated', 'document_id': 'doc-123', 'updated_fields': ['title']}

Response Codes

CodeEndpointDescription
200GET /documentsSuccess - Returns document list
200GET /document/:document_id/textSuccess - Returns ordered extracted-text chunks and document facets
200DELETESuccess - Document soft-deleted or hard-deleted
200POST /document/:document_id/restoreSuccess - Document restored
200PATCH /updateSuccess - Document metadata updated
401AllUnauthorized - Invalid API key
404GET text / DELETE / restore / PATCHNot Found - Document doesn't exist, is not visible, has no matching version, or belongs to a different client
400GET / PATCHBad Request - Invalid parameters

Error Responses

{
"detail": "Invalid API key"
}
{
"detail": "Document not found"
}
{
"detail": "Invalid metadata_filter JSON"
}
{
"detail": "metadata_filter must be a JSON object"
}

Document Summary Fields

FieldTypeDescription
document_idstringUnique document identifier
metadataobjectStructured metadata; see Metadata and Entities Reference for its system and custom groups
chunk_countintNumber of text chunks indexed
created_atstringISO 8601 timestamp
deletedbooleanPresent only when include_deleted=true; true when the document is soft-deleted
deleted_atstringPresent only when include_deleted=true; soft-delete timestamp
deleted_bystringPresent only when include_deleted=true; resolved client id that soft-deleted the document

Metadata Structure

The Metadata and Entities Reference owns the system groups, fields, and reserved-key contract for the metadata object.

Custom Metadata: Fields uploaded via the metadata parameter in /embed or /update are stored under metadata.custom.*, even when their names overlap with system fields such as author, title, or categories. These fields are filterable in both search and document listing with their short keys.

Note: The categories field is also available as a top-level payload field for efficient filtering.

Categories

Documents are automatically classified during processing. The classification section contains:

  • categories — Array of category strings (e.g., ["financial reporting", "risk management"])
  • status — Classification status: "pending" while the asynchronous callback is outstanding, "completed" on success, "failed" after recovery attempts are exhausted, or "disabled" when classification is off
Customizing Classification

Categories are assigned by the local ModernBERT ONNX classifier. You can:

  • Customize the category list via ALLOWED_CATEGORIES
  • Disable classification via CLASSIFICATION_ENABLED=false

See the Auto-Categorizing Your Documents guide and Configuration for details.

Example classification metadata:

{
"classification": {
"categories": ["financial reporting", "risk management"],
"status": "completed"
}
}

Best Practices

  1. Check before hard-deleting - Use GET /documents?include_deleted=true to verify document state before physical removal
  2. Prefer soft delete - The default delete mode hides documents from normal reads while preserving restore capability
  3. Use restore for mistakes - POST /document/:document_id/restore brings soft-deleted chunks back into normal search/list results
  4. Handle 404 gracefully - The document may not exist, may not match the requested version, or may belong to another client
  5. Use metadata for organization - Filter documents by metadata before bulk operations