Ingesting pages¶
There is one ingest endpoint, POST /v1/pages. Every new page — from a
browser extension, a history importer, or a manual add — goes through it. The
caller supplies the body text; Refindery does the rest asynchronously.
For the exact request/response contract (fields, status codes, examples), see the Upstream ingest API. This guide explains what happens to a page once it arrives.
The three body modes¶
The request carries the page body in one of three ways:
| Mode | Field | What Refindery does |
|---|---|---|
| Pre-extracted (preferred) | body_extracted |
Stores the plain text directly. |
| HTML | body_html |
Extracts main-body markdown (requires the html extra). |
| URL only | (neither) | Fetches the URL and routes by content type (HTML/PDF). |
body_extracted and body_html are mutually exclusive. URL-only ingestion
enqueues a FETCH_AND_INDEX job instead of INDEX_PAGE.
The ingest → index → enrich flow¶
POST /v1/pages ──▶ canonicalize URL ──▶ blacklist check ──▶ upsert page ──▶ enqueue job
│ │
202 Accepted (returned immediately) ▼
INDEX_PAGE / FETCH_AND_INDEX
│
extract (if needed) → chunk → embed → upsert vectors
│
status ← indexed
│
enqueue EXTRACT_ENTITIES
- Ingest (
IngestService) — canonicalize the URL (tracking params stripped, per-domain keep rules), reject blacklisted patterns with403, upsert the page (one row per canonical URL, never versioned), and enqueue a durable job. The API returns202immediately. - Index (
IndexingService, via the queue consumer) — extract content if needed, chunk it (sentence-aware, model independent), embed each chunk with the active model, and upsert chunk vectors plus a pooled page vector into the vector store. The page becomesindexed, and anEXTRACT_ENTITIESjob is enqueued. - Enrich (entities) — run the extractor chain over the page
and canonicalize mentions into entities. This is an enrichment job: its
failure is visible under
features.entitiesbut does not change the page's retrieval status.
YouTube transcripts¶
With the youtube extra installed (uv add 'refindery[youtube]'), a URL-only
ingest of a YouTube video URL fetches the video's transcript via yt-dlp
instead of the JS-heavy watch page: manual captions are preferred, then
auto-generated captions, then — when the transcribe (or transcribe-mlx on
Apple Silicon) extra is installed — the audio is downloaded and transcribed
locally with Whisper. The page's title becomes the video title and its body
the transcript. A video with no captions and no transcriber installed fails
fetch (the page eventually goes dead); HTML is never indexed for videos.
The audio path needs ffmpeg on the host. Configure with
REFINDERY_FETCH__YOUTUBE_* (languages, auto-caption opt-out, Whisper model);
to watch whole playlists/channels see Watches.
Audio transcription¶
With a local Whisper transcriber installed (the transcribe extra, or
transcribe-mlx on Apple Silicon, plus ffmpeg), a URL-only ingest of an
audio file URL (.mp3, .m4a, .ogg, .opus, .wav, .flac, …) is
transcribed instead of fetched as a page: the audio streams to a temp file —
SSRF-guarded, capped by REFINDERY_FETCH__AUDIO_MAX_BYTES (default 250 MB,
far above the 10 MB in-memory page cap) — and the transcript becomes the
page body. A response that is not audio (an HTML error page at an .mp3
URL) fails the fetch before any transcription. The Whisper model is shared
with REFINDERY_FETCH__YOUTUBE_WHISPER_MODEL; disable the whole path with
REFINDERY_FETCH__AUDIO_TRANSCRIPTS=false, after which audio URLs fetch as
plain pages again. To follow podcasts see Watches. One dedup
caveat: hosts that re-publish enclosures with changed cache-buster params
(e.g. ?updated=) create a new page per variant — add such params to
REFINDERY_CANONICALIZE__TRACKING_PARAMS to collapse them.
URL canonicalization¶
Two requests for the "same" page must collapse to one row. Canonicalization
lowercases the scheme and host, strips the default port, www., the fragment,
and tracking parameters (utm_*, fbclid, gclid, ref, si, …), sorts the
remaining query params, and strips the trailing slash. Per-domain overrides
apply where the query matters (e.g. YouTube keeps only v), and YouTube video
URL forms (youtu.be/<id>, /shorts/<id>, /live/<id>) fold into
youtube.com/watch?v=<id> so every form of one video dedups to one page.
Configure overrides with REFINDERY_CANONICALIZE__*.
Revisit semantics¶
When a POST arrives for a canonical_url that already exists:
last_seen_atis set to now andvisit_countis incremented;- the supplied
body_extracted,body_html, or absent body is discarded, even if its content hash differs; - the response reports
revisit: trueplus a differing-hash flag for observability.
Two distinct URLs with identical content remain two pages in v1 — the clusterer co-locates them. (Content hashes are stored so that content-mediated collapse can be added in v2.)
Page lifecycle¶
Only indexed pages are used by search, similarity, and suggestions. Core
indexing failures best-effort delete partial chunks and vectors before marking
the page failed; dead jobs are queryable and manually re-enqueueable. See
Operations for the job lease and recovery model, and
the status reference.
Related¶
- Upstream ingest API — the full contract for capture clients.
- Tuning — chunking and fetch limits.
- Deletion & blacklist — removing content and blocking re-ingestion.