Build a live RAG pipeline with Apify, n8n, and Qdrant Apify published a guide for building a live retrieval-augmented generation (RAG) pipeline using Apify's Website Content Crawler, self-hosted n8n, and Qdrant that re-embeds only changed pages and removes deleted pages from the vector index. The guide notes that n8n 2.37.10 still lacks built-in vector store record management, a gap a community member confirmed in December 2024, so the common workaround is deleting the entire collection before every load. The 7-node workflow runs on a webhook triggered when an Apify crawl finishes, and the build takes about an hour once accounts exist. Your crawler runs, the chunks reach the vector store, and the chatbot answers. Then, the pricing page changes. A product page is deleted. It keeps answering from the chunks it stored last week, confidently and wrongly, and nothing reports an error. The crawl ran again. Nothing removed the old chunks or replaced the changed ones. This guide builds an n8n RAG pipeline that does both. Website Content Crawler https://apify.com/apify/website-content-crawler crawls the site into an Apify dataset. It's an Actor, the Apify term for a ready-made cloud program that you configure and run rather than build. A webhook starts n8n when the crawl finishes, and a second Actor chunks the pages, embeds them, and writes them into Qdrant. Only the pages that changed are re-embedded, and pages deleted from the site are removed from the index. The workflow runs on self-hosted n8n. This build takes about an hour once the accounts exist, and longer if you also need a tunnel in front of n8n. You need to know how to set environment variables and run n8n yourself. What goes wrong on the second run On the second run, 3 things happen. The crawler returns the same pages, and the vector store node inserts a second copy of each. A page that changed since the first crawl now has 2 versions stored, and search returns both. Nothing removes a page deleted from the site, so its chunks remain. No log line marks any of it. Answer quality drops instead, which is much harder to notice. How retrieval-augmented generation works https://blog.apify.com/what-is-retrieval-augmented-generation/ is well documented. Keeping that index correct over time isn't. The n8n community forum has an open thread asking for record management in vector stores https://community.n8n.io/t/vector-store-record-management-in-n8n/66833 . The answer from a community member in December 2024 was direct: there's no built-in record management feature for vector databases in n8n. That still holds in n8n 2.37.10, and Step 3 has the per-node breakdown. The workaround people use is to delete the whole collection before every load. That workaround works, and on a small site it's enough. On a large one it's expensive, and the collection is only partly loaded while the rebuild runs. What you will build The alternative to deleting the whole collection every night is to compare each page against what is already stored and write only the difference. The workflow has 7 nodes: php Apify Trigger - Vectors before sync - Sync dataset into Qdrant - Let the sync settle - Vectors after sync - Read sync log - Freshness report Here’s the n8n editor view: Only 3 of the 7 nodes do the work. The other 4 exist to prove it happened: the counts on either side of the sync, plus Read sync log and Freshness report . You never select Run on this workflow. You schedule the crawl instead, on the Apify side. When the crawl finishes, the webhook starts n8n and the 7 nodes run in order. The report shows what changed on each run. The dataset in the middle is a saved copy. You can inspect what the crawl collected and compare it against the previous night. Replaying it into the database costs no new crawl. Before you start You need 4 accounts or installs, and 3 of them are free at this scale: - An Apify account. A free plan includes a monthly platform allowance and needs no credit card. Current limits are on the Apify pricing page https://apify.com/pricing . - n8n, self-hosted. The Community edition is free to run on your own hardware under the n8n fair-code license. Check the n8n pricing page https://n8n.io/pricing/ for current terms. A self-hosted instance also has to be reachable from the public internet, which is what lets Apify deliver the webhook in Step 2. On a laptop that means putting a tunnel in front of n8n, so arrange it now. - A Qdrant Cloud cluster. The free plan includes 1 GB RAM and 4 GB disk, which is far more than this corpus needs. Current limits are on the Qdrant pricing page https://qdrant.tech/pricing/ . - An OpenAI API key for embeddings. This is the paid item, and embedding this corpus cost about $0.003 at list price on the first run. Set the environment variables QDRANT API KEY and OPENAI API KEY come from the n8n environment. The workflow passes OPENAI API KEY inside a JSON body to an Actor. It uses QDRANT API KEY both there and as an api-key header on both count nodes. Set both where your instance reads its environment. That's a -e flag on docker run , the environment: block in Docker Compose, or a shell export for an npm install. Then set N8N BLOCK ENV ACCESS IN NODE=false explicitly. On n8n 2.x that switch blocks expressions from reading the environment unless it's set to the string false . Leave it unset and every $env read returns access to env vars denied . All 3 go in the same place, and n8n reads the environment at startup, so restart it after any change. The switch is instance-wide and applies to everything or to nothing. It doesn't grant this workflow access to those 2 variables. It grants every expression in every workflow on that instance access to the whole process environment. A production install usually holds more than 2 API keys. It commonly holds the database password, and N8N ENCRYPTION KEY where the operator sets that explicitly rather than letting n8n generate one. That key decrypts every credential that the instance stores. Set the switch only where every workflow author is already trusted with everything in that environment. On a shared instance, put the Qdrant key in a Header Auth credential on both count nodes instead. That removes 2 of the 4 $env reads, and a credential needs no environment access. Install the Apify community node A new n8n install doesn't include the Apify community node. Add it under Settings Community Nodes with the package name @apify/n8n-nodes-apify . Do not run npm install in the nodes folder yourself. n8n strips the package's peer dependencies and passes --ignore-scripts before installing. A plain npm install pulls n8n-workflow instead, then fails trying to compile isolated-vm against the image's Node version. The Apify Trigger node needs a credential, and so do the 2 HTTP Request nodes that call the Apify API. Copy a token from the API & Integrations https://console.apify.com/settings/integrations page in Apify Console, and in n8n create a credential of type Apify account with it. Creating the credential and attaching it to a node are separate steps, and it's common to do the first and forget the second. Miss either step and nothing shows an error where you're looking. The workflow publishes, and the trigger then fails to start. The failure appears in a retry loop visible only in the n8n log, first as Unrecognized node type: @apify/n8n-nodes-apify.apifyTrigger . Once the node is present but no credential is attached, it fails as No valid credentials found for apifyApi . Import the workflow n8n takes live-rag-pipeline.n8n.json https://gist.github.com/triposat/f493457342214fb41b71fa80a3f74c16 directly through Import workflow from URL , using the raw link to that file. It needs 2 edits now: 1. Replace the placeholder cluster URL in 3 places. This is a plain URL rather than a credential, so n8n never prompts you for it. Your cluster's address is on its overview page in Qdrant Cloud, labeled Endpoint . Copy it with the :6333 port at the end. The 3 places are: - The url field on Vectors before sync - The url field on Vectors after sync - The qdrantUrl line inside the body of Sync dataset into Qdrant 2. The 3. Attach your Apify credential to the 3 nodes that use it. Step 1: Crawl the site into a dataset Web scraping for RAG has to produce clean text rather than whole pages. Website Content Crawler apify/website-content-crawler crawls a site and strips navigation, footers, and modals before the text reaches you, which is what a text splitter needs. It writes a cleaned text field on each item that it stores, and that's the field that this pipeline embeds. You run a single crawl by hand here. There are 3 settings that control what it costs and how much of the site it reaches, and each one needs a decision from you. Set the crawler type and memory explicitly Apify input schemas carry 2 separate values for a field, and the input schema specification https://docs.apify.com/platform/actors/development/actor-definition/input-schema/specification/v1 defines both. A prefill "is only used in the user interface." A default applies whenever no value is given "via any means API, CLI, scheduler, or user interface ." For crawlerType those 2 values differ, and the half that costs you money is the one you never see. The schema default has stayed at playwright:firefox through every build change. Any run that names no crawler opens a headless browser on every page, whether the caller is an n8n node, a schedule, or the API. The prefill is the unstable half. It changed twice in 2 days across builds 0.3.96 and 0.3.97, in both directions. So name crawlerType explicitly in your input, and the question stops mattering. Leaving it unnamed has a measurable cost. These 3 runs crawled the same 4 pages of Apify documentation at 4,096 MB on September 4, 2026: | Run | crawlerType | Handler used | Runtime | Cost | |---|---|---|---|---| | A | not set | browser on 4 of 4 pages | 95.9s | $0.02415 | | B | cheerio | HTTP on 4 of 4 pages | 16.1s | $0.00579 | | C | playwright:adaptive | browser on 4 of 4 pages | 110.6s | $0.02787 | Compared with run B, the unset default cost 4.17 times as much and adaptive cost 4.81 times as much. Adaptive runs rendering-type detection and then still opens a browser. Memory is the second setting that you can change. Website Content Crawler declares memoryMbytes: 8192 in its default run options, so a caller that passes no memory value runs at 8 GB. Apify bills compute units as memory multiplied by runtime, which makes that a direct multiplier on the crawl bill. Set it explicitly. Check the start URL before you schedule anything In early September 2026 https://docs.apify.com/integrations redirected to /platform/integrations . Seeding the redirecting URL and running the same configuration 8 times returned 10, 28, 41, 44, 45, 49, 53, and 86 pages. Only 1 of those 8 runs reached the full site. Seeding the post-redirect URL instead returned 86 pages on 3 runs, with identical URL sets. Apify has since removed that redirect. The check still applies, because any seed URL can start redirecting at any time: curl -sI