What you need
- An Outcrawl API key in
OUTCRAWL_API_KEY. - Postgres with the
pgvectorextension, inDATABASE_URL. The program creates its two tables. - An embeddings key. The program uses OpenAI’s
text-embedding-3-small(OPENAI_API_KEY); any embedding model works if you change the vector size. npm install @outcrawl/sdk openai pgorpip install outcrawl openai "psycopg[binary]".
How it works
1
Crawl the section
A crawl starts at the section root, stays inside
includePaths, and streams each page as clean markdown with its title and fetch time. Nothing waits for the whole site.2
Skip what has not changed
Each page’s markdown is hashed and compared with the hash stored last time. Only new or changed pages are chunked and embedded. Pages that came back empty or behind an interstitial are skipped, so a bad fetch never replaces a good copy.
3
Chunk by heading
Chunks follow the page’s own headings, up to about 1,500 characters, and keep the heading path (
Workflows > Triggers > schedule) as metadata. Headings inside code blocks are ignored.4
Write a page in one transaction
The page row and its chunks are replaced together, so a search never sees half of an update.
5
Remove what is gone, carefully
Pages missing from a crawl that finished below its
limit are deleted with their chunks. A crawl that failed or hit the limit deletes nothing.The program
0 3 * * * cd /srv/kb && npx tsx kb.ts (or python kb.py) in cron. The first run embeds everything; later runs embed only the difference.
What you get back
The console, on a night when the docs team shipped two edits and retired a page:kb_chunks:
select url, heading, content from kb_chunks order by embedding <=> $1 limit 8, and every chunk carries the URL your assistant cites.
What it costs
A crawl is 1 credit per page. A 400-page docs section is 400 credits per crawl, whether or not the pages changed, because a page has to be read to know.
The saving is on your side: only changed pages are embedded and written, so a quiet night costs your embedding provider almost nothing.
Make it yours
- Put a monitor on the docs changelog. It calls a webhook endpoint of yours when the page changes, and your endpoint starts this crawl the moment something ships instead of waiting for night.
- Crawl several sections or sites in one run by looping over
[root, includePaths]pairs; the stored hashes already work for any URL. - Store
page.metadata.languagetoo and filter retrieval by the user’s language. - Replace pgvector with your own vector store: the upsert is the only part that changes.
Related
Crawl
Limits, paths and streaming.
Crawl jobs
Recover a crawl’s pages if the stream drops.
Monitors
A webhook when a page changes.
Pricing
Every rate on this page.