> Source: https://txtfetch.com/ingest/qdrant > Plain-text twin — every page on txtfetch.com has one. https://txtfetch.com/text --- # Extract text into Qdrant An open-source and managed vector database with a strict, easy-to-miss ID format. Here's what changes between the text txtfetch returns and the first row you write to Qdrant. ## The problem Qdrant's payload holds arbitrary JSON, so most chunk records fit without changes. The trap is the point ID: Qdrant only accepts an unsigned 64-bit integer or a UUID string. A chunk ID scheme like "doc-3-chunk-9" is rejected outright and needs converting first. what qdrant needs | Per-vector metadata limit | No fixed cap on a point's payload. Bound by the request-size ceiling instead: 32 MB per REST call by default, or a much smaller 4 MB default on the gRPC client unless you raise it. | | --- | --- | | Stores the chunk text itself | Yes. The payload is arbitrary JSON stored with the point, so the chunk text is a normal payload field. | | ID format | An unsigned 64-bit integer or a UUID string. No other string is accepted. | | Max vector dimensions | 65,535 dimensions for a dense vector. | | Batch upsert size | No hard point-count cap. Qdrant's own guidance suggests 64 to 256 points per batch, with a few parallel upload threads for a big job. | | Index type | HNSW, with optional scalar or binary quantization to cut memory use. | No accuracy, speed, or recall figure is measured against Qdrant on this page. See [/benchmarks](https://txtfetch.com/benchmarks) for txtfetch's own measured extraction numbers. the pipeline Extract, chunk, embed, and upsert. No framework, just the [txtfetch Python SDK](https://txtfetch.com/docs/quickstarts) and Qdrant's own client. Python ```python import os import uuid from openai import OpenAI from qdrant_client import QdrantClient from qdrant_client.models import Distance, PointStruct, VectorParams from txtfetch import Txtfetch txtfetch = Txtfetch(api_key=os.environ["TXTFETCH_KEY"]) oai = OpenAI() client = QdrantClient(url=os.environ["QDRANT_URL"], api_key=os.environ["QDRANT_API_KEY"]) # create_collection raises if the collection is already there, so a second # run of this script needs the guard. if not client.collection_exists("docs"): client.create_collection( collection_name="docs", vectors_config=VectorParams(size=1536, distance=Distance.COSINE), ) def chunk_text(text: str, size: int = 1000, overlap: int = 100) -> list[str]: chunks = [] start = 0 while start < len(text): end = start + size chunks.append(text[start:end]) start = end - overlap return chunks # 1. Extract result = txtfetch.extract(file="whitepaper.pdf") # 2. Chunk chunks = chunk_text(result.extracted_text) # 3. Embed response = oai.embeddings.create(model="text-embedding-3-small", input=chunks) # 4. Upsert — point ids must be an unsigned 64-bit integer or a UUID, so a # generated UUID replaces any string-based chunk id scheme. points = [ PointStruct( id=str(uuid.uuid4()), vector=item.embedding, payload={"source": "whitepaper.pdf", "text": chunk}, ) for chunk, item in zip(chunks, response.data) ] BATCH_SIZE = 128 for i in range(0, len(points), BATCH_SIZE): client.upsert(collection_name="docs", wait=True, points=points[i : i + BATCH_SIZE]) ``` Every call above was resolved against qdrant-client 1.16.1, in September 2026. Client APIs move, so check the vendor's own docs for the version you pin. ## Chunking for Qdrant Qdrant's payload carries no size ceiling of its own, only the surrounding request-size limit. Chunk length is your call, not Qdrant's. The real trap is the point ID. A chunk ID scheme like "doc-3-chunk-9" needs converting to a UUID first, since Qdrant only accepts an integer or a UUID. Check your chunk boundaries in the chunk previewer before you generate ids for the whole batch. large batches A large file, or a slow OCR pass, routes to an async job automatically. That returns a `202` plus a `job_id` to poll, and `?async=true` forces that path for any request. Pass `webhook_url` instead, and txtfetch calls you back when the text is ready, rather than you polling for it. See [async jobs & webhooks](https://txtfetch.com/docs/async) for the full lifecycle. Submit (async) ```submit curl -X POST "https://api.txtfetch.com/v1/extract?url=https://example.com/whitepaper.pdf&async=true" \ -H "Authorization: Bearer $TXTFETCH_KEY" # {"status": "processing", "job_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"} ``` Poll ```poll curl "https://api.txtfetch.com/v1/extract/3fa85f64-5717-4562-b3fc-2c963f66afa6" \ -H "Authorization: Bearer $TXTFETCH_KEY" # {"status": "processing", "job_id": "..."} while running, then the same # {"status": "success", "extracted_text": "...", "metadata": {...}} shape # POST /v1/extract would have returned synchronously. ``` curl ```curl curl -X POST "https://api.txtfetch.com/v1/extract?url=https://example.com/whitepaper.pdf" \ -H "Authorization: Bearer $TXTFETCH_KEY" \ --data-urlencode "webhook_url=https://example.com/webhooks/txtfetch" ``` A retried request, from a client timeout or a dropped response, shouldn't extract and upsert the same document twice. Send the same `Idempotency-Key` on a retry and txtfetch returns the first result instead of running the job again. See [idempotency](https://txtfetch.com/docs/idempotency) for the full guarantee. curl ```curl curl -X POST https://api.txtfetch.com/v1/extract \ -H "Authorization: Bearer $TXTFETCH_KEY" \ -H "Idempotency-Key: 3f29b6e4-9c1a-4b8e-9c2a-1e6f0a2d5b3c" \ -F file=@contract.pdf ``` JavaScript ```javascript // The SDK auto-generates and reuses an Idempotency-Key across its own // retry chain. Pass your own to control it explicitly: await txtfetch.extract({ file: "./contract.pdf", idempotencyKey: "3f29b6e4-..." }); ``` Python ```python # The SDK auto-generates and reuses an Idempotency-Key across its own # retry chain. Pass your own to control it explicitly: client.extract(file="contract.pdf", idempotency_key="3f29b6e4-...") ``` txtfetch ships no connector, plugin, or client for Qdrant. The pipeline above is the whole integration. Extract with txtfetch, then write to Qdrant with Qdrant's own client, the same as you would for any other source of text. frequently asked questions **Does txtfetch write directly to Qdrant?**: No. txtfetch returns extracted text over one HTTP call. Chunking, embedding, and the upsert itself stay a caller-side step, exactly like the pipeline above shows. **Why does the point ID format trip people up?**: Qdrant only accepts an unsigned 64-bit integer or a UUID string as a point id. A chunk ID scheme built from a document name and chunk index, like "doc-3-chunk-9", is rejected outright until you convert it to a UUID. **Does txtfetch benchmark accuracy or speed against Qdrant?**: No. Qdrant is a vector database, not a text-extraction service, so there's no accuracy or speed comparison to make. See /benchmarks for txtfetch's own measured extraction numbers. sources - [Qdrant: Quickstart (client, collection, upsert)](https://qdrant.tech/documentation/quickstart/) Accessed 2026-09 - [Qdrant: Points concept (payload, point ID rules)](https://qdrant.tech/documentation/concepts/points/) Accessed 2026-09 - [Qdrant: Indexing concept (HNSW, quantization)](https://qdrant.tech/documentation/concepts/indexing/) Accessed 2026-09 - [Qdrant: Bulk upload guide (batching)](https://qdrant.tech/documentation/manage-data/bulk-upload/) Accessed 2026-09 - [Qdrant: FAQ (dimension limits)](https://qdrant.tech/documentation/faq/qdrant-fundamentals/) Accessed 2026-09 - [Qdrant: Configuration reference (request-size limits)](https://qdrant.tech/documentation/guides/configuration/) Accessed 2026-09 ## Related - [RAG & LLM ingestion](https://txtfetch.com/solutions/rag-ingestion), the wider use case this pipeline feeds. - [RAG recipe: chunk, embed, index](https://txtfetch.com/docs/recipe), the same pipeline through LangChain or LlamaIndex instead. - [Chunk previewer](https://txtfetch.com/tools/chunk-preview), to test your own extracted text before you write the first row. - [Async jobs & webhooks](https://txtfetch.com/docs/async), the full lifecycle for a large batch. other stores - [Extract text into pgvector →](https://txtfetch.com/ingest/pgvector) - [Extract text into Pinecone →](https://txtfetch.com/ingest/pinecone) - [Extract text into Chroma →](https://txtfetch.com/ingest/chroma) - [Extract text into Weaviate →](https://txtfetch.com/ingest/weaviate) - [All destinations →](https://txtfetch.com/ingest) ## Wire the extraction step in. The RAG recipe shows the whole path, from a file to the Qdrant upsert. [Read the RAG recipe →](https://txtfetch.com/docs/recipe) [Get an API key →](https://app.txtfetch.com/signup)