Skip to main content
A support assistant is only as good as the docs it retrieves, and docs change every week. Re-embedding a whole site every night wastes money, and never re-embedding it means the assistant quotes last quarter’s settings. This program crawls a docs section into heading-aware chunks with metadata, stores them in Postgres with pgvector, and on every later run re-embeds only the pages whose content changed and removes the ones that are gone.

What you need

  • An Outcrawl API key in OUTCRAWL_API_KEY.
  • Postgres with the pgvector extension, in DATABASE_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 pg or pip 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

Run it nightly from any scheduler, for example 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:
One row of kb_chunks:
Retrieval is one query: 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.language too and filter retrieval by the user’s language.
  • Replace pgvector with your own vector store: the upsert is the only part that changes.

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.