Configuration (config.toml)¶
Copy config.minimal.toml for a first run (email, paths, open-access sources)
or config.example.toml for the full file. Both are tracked; config.toml is not.
Docker: prefer relative out_dir / state_dir ("out" / "state"). Paths
with ~/… expand to the container user’s home (not the Compose /data mount),
so host session login and docker compose run will disagree about where
cookies live. paperful doctor ambers when that happens.
Grey literature and no-DOI items — Unpaywall and most DOI sources cannot
resolve PrepCom papers, many DOALOS/UN docs, or undocs without a DOI. direct
uses declarative grey playbooks (rewrite / scrape / synthesize). Grey-lit
packs: UNGA/undocs · BBNJ/DOALOS · ISA, plus FAO and ocean hosts
(RFMOs, IUCN/DOSI, IDDRI/Pew). Energy (IEA, IRENA) and other international
orgs (OECD, WHO, UNEP, UNDP) ship as optional example files under
paperful/data/grey_playbooks_examples/. Add your own hosts in config.toml.
Skip-host URLs (YouTube, Scholar, …) still
synthesize from Extra/title when a playbook matches. Then htmlpdf can print
DOI-less document / report pages. Otherwise the manifest records
no_identifier. See Grey literature playbooks
and architecture.
Key |
Default |
Meaning |
|---|---|---|
|
|
Contact address for Unpaywall, Crossref, NCBI, and polite OpenAlex use. Required when |
|
|
Library adapter. |
|
|
Elsevier OAuth app from dev.mendeley.com/myapps.html. Or |
|
|
Must match the app. Host-only ( |
|
(none) |
Path to the |
|
|
PDF tree; manifest, patches, PDF cache, run reports, and write key |
|
|
PDFs the manager already holds: |
|
|
|
|
|
An item trashed, merged, or deleted in the manager: |
|
|
Firefox capture only. |
|
|
Collection path for a page that is in neither the mirror nor Zotero |
|
(empty) |
Where the add-on stages PDFs and HTML snapshots. Empty uses |
|
|
How many selected tabs the add-on saves at once (1–4) |
|
|
Which Unpaywall / OpenAlex fields are written to |
|
|
Substrings; when a stamped |
|
(none) |
Protect collection subtrees from library writes. See Exclude / protect collections |
|
|
Where the readable lines go: where a PDF came from, which duplicate to keep, and why a snowball hit belongs. |
|
|
Default head of a run. |
|
|
|
|
|
|
|
|
Paid Google Scholar link discovery after local routes. Needs env |
|
|
Paid Scholar searches this run. |
|
|
Opt-in Camoufox recover lane after vault browsers, before |
|
|
Inject on |
|
|
REST base for the camofox-browser server |
|
|
Wall-clock budget per item for the scripted grab |
|
|
Opt-in GROBID sidecar for |
|
|
REST base. Loopback is rewritten to |
|
|
Full-text POST budget |
|
|
Permit a non-local host. PDF bytes then leave this machine |
|
|
GROBID Crossref header consolidate. Keep |
|
|
GROBID Crossref citation consolidate. Keep |
|
|
Opt-in: |
|
|
Put a Scholar results URL on miss rows and open it in the system browser when there is no direct PDF URL |
|
|
Author-request policy: |
|
|
Used when |
|
off |
Optional CRM and metasearch for author pages. Not part of a normal fill. Knobs and what the products are: Twenty and SearXNG |
|
|
Check library DOIs against Crossref/OpenAlex before fetching; may swap DOI in memory for that run. |
|
|
CORE API bearer token (register); empty skips the |
|
|
Campus proxy prefix ending in |
|
|
On a TTY, prompt to re-login before batch 1 (failed probe), at the next batch boundary mid-run, and after the fetch for session-expired items. |
|
|
Compat Netscape dump after |
|
|
Compat Netscape dump after |
|
|
Load the packaged ocean/governance example pack |
|
(none) |
Directory of extra pack |
|
|
|
|
|
How many matching wins |
|
(none) |
User rewrite/scrape/synthesize rules; same |
|
|
CLI: attach into Zotero after |
|
|
Discover map library panel: |
|
|
After Add to library on the map, also run Find PDF for that item |
|
|
Map Remove from library behaviour (trash only for now) |
|
|
Discover People co-author map: |
|
|
Discover Web tab (SearXNG grey search): |
|
(unset) |
Env / flag, not a |
|
|
Rows per page on Library and Wanted, and the choices in the per-page select |
|
|
Default kept hits for |
|
|
HEAD/GET first bytes for PDF vs HTML. CLI |
|
|
Name shown in Zotero’s authorisation dialog |
Leave ezproxy_base empty (or remove ezproxy from sources) if you do not
use a library proxy. Google Scholar is off until you add scholar to
sources (and usually run session login scholar). Policy mode then runs it
late, not in the middle of campus/grey. Paid SerpApi Scholar
search is off until [serpapi].enabled and SERPAPI_API_KEY; cap with
[serpapi].max_calls / --serpapi-max. Opt-in Camofox is off
until [camofox].enabled and a reachable server at [camofox].base_url.
Opt-in GROBID is off until [grobid].enabled and a reachable
server at [grobid].base_url.
Sci-Hub is off until
you add "scihub" to sources or pass --scihub — see Sci-Hub.
Items dated after 2021 are not sent to Sci-Hub; a --year-from past that
year drops it from the run list.
Tuning knobs (thresholds, concurrency, User-Agent, OpenAlex snapshot hosting) live under Advanced.
Run configs (profiles)¶
A profile stores one SCOPE plus the fetch/write flags you would otherwise repeat on the command line. It is not a grey-lit playbook and not a pack. Full recipes: Workflows.
Term |
Meaning |
|---|---|
Profile / run config |
Named SCOPE + policy ( |
|
|
|
Open-access sources only (the default list without |
|
OA + EZProxy; no Scholar, no Sci-Hub. Same names as the default |
Playbook |
Grey-lit URL → PDF rule |
Pack |
Witness of one executed sequence under |
Snowball profile |
|
Where files live. Beside config.toml:
[profiles.<name>]tables in that fileprofiles/<name>.tomlin the same directory (overlay: file wins per key)
Relative profiles/ follows the config file, including Compose /data.
Profiles are not written under state/ (that tree is backup-excluded).
Precedence for paperful all and paperful profile show:
Builtin
allpolicy when the profile omits a key: stepsgaps,run,lint,fix-metadata,summarize;try_all,retry_failed,upgrade_linked, andapplytrue[profiles.*]profiles/<name>.toml--profileor-f/--run-configExplicit CLI flags
A bare paperful run --profile does not inherit the builtin all flags.
Omitted try_all stays off unless the profile sets it.
TOML key |
CLI |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
profile save NAME writes profiles/NAME.toml from the flags on that
invocation (plus --profile / -f if you are copying one). It refuses to
overwrite unless --force. It does not edit config.toml.
[profiles.bbnj-journal]
description = "BBNJ journal articles 2021–2026"
collections = ["BBNJ"]
types = ["journalArticle"]
year_from = 2021
year_to = 2026
try_all = true
retry_failed = true
upgrade_linked = true
apply = true
Grey literature playbooks¶
Host-specific PDF rules are data, not forever-hardcoded Python. Kinds:
Kind |
When |
Example |
|---|---|---|
|
Zero-fetch URL → PDF ( |
FAO |
|
Prefer matching hrefs on that host’s HTML landing |
ISA |
|
Extra/title (or skip-host URL + Extra) → PDF URL |
UN document symbol → undocs |
The packaged file
paperful/data/grey_playbooks_ocean.toml
is an ocean/governance example pack — grey-lit packs UNGA/undocs ·
BBNJ/DOALOS · ISA, plus FAO, RFMO, IUCN/DOSI, and IDDRI/Pew — on by default
via grey_playbooks_builtin = true. IEA/IRENA and OECD/WHO/UNEP/UNDP are
optional copies in
paperful/data/grey_playbooks_examples/.
Copy those files into grey_playbooks_dir, or point that setting at the
examples folder. That folder does not contain the ocean builtin, so it will
not replace the shipped pack by name. Optionally set
grey_playbooks_dir = "packs" to load every *.toml in that directory (same
schema). Merge order: builtin → dir packs → inline [[grey_playbooks]]
(same name replaces earlier entries). Host ownership in the shipped files:
un.org, highseasalliance.org, and iisd.org belong to
bbnj-doalos-prepcom; fao.org belongs to fao; iea.org belongs to the
energy example. These rules turn a library item’s URL into a PDF. A matching
host does not mean Paperful indexes that organisation. Provenance stays
grey:<playbook-name>.
paperful playbooks promote adds learned.toml in that directory from local
fetch wins. Those recipes are not in the package. [playbooks].promote = "auto"
writes them during run and may promote a fluke; the default is gated.
PMC / arXiv / HAL stay as core OA rewrites, not playbooks. See
architecture § Grey literature for hosts
and symbol patterns. If you use the optional Docker image, put packs next to
config under /data (see Docker).
grey_playbooks_builtin = true
grey_playbooks_dir = "packs"
# [playbooks]
# promote = "gated" # gated | auto — auto may promote a fluke
# auto_min_hits = 2
[[grey_playbooks]]
name = "my_org"
kind = "rewrite"
hosts = ["example.org"]
url_re = '(?i)example\\.org/docs/(?P<code>[a-z0-9]+)/?'
pdf_template = "https://example.org/docs/{code}/{code}.pdf"
LLM (optional, local-first)¶
Off by default. Install extras: uv sync --extra llm (LiteLLM for paid APIs),
uv sync --extra browser-agent (Python 3.11+ only, for the browser_agent run
lane and recover), or build the heavy Compose image
(PAPERFUL_IMAGE_MODE=heavy; see Docker).
Batch recover: recover --from-last-run reads state/last-run.json
(--from-last-run-mode, --limit). Feasibility (try-it vs library scale,
OpenRouter / cost sketch): AI. Setup walkthrough, verbs, Docker
networking, and troubleshooting: LLM.
Table / key |
Default |
Role |
|---|---|---|
|
|
Master gate |
|
|
|
|
|
Model id |
|
|
Ollama API. Inside Compose, a loopback URL is rewritten to |
|
|
OpenAI-compatible base when |
|
|
Grounded title proposals in |
|
|
|
|
|
Which lanes run (these are the ids the config accepts). PubPeer needs |
|
|
Minimum tortured-phrase hits before flagging (obvious phrases always flag) |
|
|
PubPeer batch lookup |
|
|
Re-download PPS CSV dumps after this age ( |
|
unset |
Optional folder with |
|
|
Child note tag for |
|
|
Shim for lane |
|
|
Suggested review lanes to run together: |
|
|
Child note tag for |
|
|
Optional Binoculars extra ( |
|
|
Optional Vale house-style pass. |
|
unset |
Path to your |
|
|
Lowest Vale level to keep: |
|
|
Subprocess timeout for one Vale run |
|
|
Optional PaperGuard subprocess. |
|
|
Subprocess timeout for one PaperGuard scan |
|
|
Fuzzy house-list acronym expansions from |
|
|
Directory under |
|
|
Minimum |
|
|
Shim for lane |
|
|
Leader turns per aspect group |
|
|
Paper chunks (worker agents) |
|
|
Cap after the refine pass |
|
|
Extra pass to drop or split comments |
|
|
Wall clock for the MARG-S pass ( |
|
|
MARG aspect ids. Empty → |
|
|
Cap on extracted claims for the |
|
|
Cap on |
|
|
Wall clock for the |
|
|
Wall clock for the |
|
|
Cap on suggested comments from |
|
|
Max logical segments in the |
|
|
Wall clock for |
|
|
Cap on suggested comments from the context pipeline |
|
|
Local RAG hits and OpenAlex / SearXNG rows for historian / scout |
|
|
Cap on suggested comments from the |
|
|
Cap on suggested comments from the |
|
one implicit reviewer |
|
|
|
Enabled rewrite specialists: |
|
|
|
|
|
|
|
|
Cap after merge |
|
|
Wall clock for LLM specialists ( |
|
|
After writing |
|
|
|
|
builtin per enabled area |
|
|
|
Distinctive n-grams ranked for library text overlap |
|
|
Word n-gram window |
|
|
Drop grams in this many other docs ( |
|
|
|
|
|
LLM excerpt budget |
|
|
|
|
|
Summarize queue order before |
|
|
Summary writing style: |
|
|
When |
|
unset |
Optional path to an approved-word list (one word per line). Relative paths resolve from the config file. Domain words from the PDF/metadata stay allowed |
|
|
|
|
|
When |
|
|
Default for |
|
|
Newest |
|
|
PDF drop folder for |
|
|
After |
|
|
Poll interval while watching |
|
|
Require stable file size before ingest |
|
|
Stop after this many idle seconds ( |
|
|
|
|
|
Seconds to keep an unmatched PDF in the drop folder before |
|
|
Transient OCR for matching only; does not rewrite the drop file |
|
|
|
|
|
LLM |
|
|
High bar for auto-attach; otherwise a gated proposal |
|
|
|
|
|
|
|
|
|
|
|
Below this, routing is thin (proposal / |
|
|
High bar for |
|
|
|
|
|
When true, |
|
|
Leave the HTML print in place when a native PDF attaches. |
|
|
Opt-in Crossref/OpenAlex title search when local title+year is thin (not silent create) |
|
|
Wait after attach so a manager recognizer can run ( |
|
|
Tags on |
|
|
Default days between |
|
|
Held RAG passages kept by |
|
|
OpenAlex / similar works kept. CLI |
|
|
Also resolve OpenAlex keyword tags for the phrase (same union as snowball search) |
|
|
Extra library sources per claim. CLI |
|
|
Manuscript lane: skip captions / headings / fragments |
|
|
Outline lane: skip empty/tiny section queries (thin headings still search) |
|
|
Cap on searched claims; overflow rows skip as |
|
|
|
|
|
Extra tags on snowball |
|
|
OpenAlex study design filter ( |
|
|
OpenAlex trusted journal lists ( |
|
ten built-ins |
Which literature-category ids appear on the citation map (badges/filters). Defaults include study-design ids ( |
|
|
Extra category tables: |
|
|
|
|
|
Marginal yield: Keep rate below |
|
|
Fitted Keep ceiling. Needs that many labeled hops. Config and profile only |
|
|
|
|
|
After create: |
|
|
Co-author graph + proposed |
|
— |
Not parsed. Caps are |
|
|
SearXNG JSON endpoint for |
|
|
|
|
|
Tesseract languages for |
|
|
After |
|
|
Master switch for |
|
|
Index new PDFs after |
|
|
|
|
|
|
|
|
|
|
|
Embedding model. |
|
|
Ollama root / LiteLLM base URL for embeddings; empty uses |
|
|
Passages per embedding request |
|
|
Passage size and overlap in characters. Changing them re-indexes on the next ingest |
|
|
Passages sent to the chat model per question in simple mode, and for |
|
|
Cap on excerpt text in the prompt |
|
|
Blend vector and full-text search; falls back to vector only |
|
|
Index the abstract when an item has no readable PDF |
|
|
Chat model for |
|
|
Prompt preset for |
|
|
Custom system prompt file; when set, overrides |
|
|
Batch ask note destination: |
|
|
Default for |
|
|
Global default for Ask. |
|
|
Cap on passages from one paper in the retrieved set |
|
|
Extra index rows fetched so the per-item cap can still fill |
|
|
Advanced: candidate passages to summarize and score. Ignored in simple mode |
|
|
Advanced: papers kept after scoring |
|
|
Advanced: drop scores below this (inclusive). Score 0 always drops |
|
|
Advanced: model for passage summaries; empty uses |
|
|
Advanced: length hint in the summary prompt |
|
|
Advanced: parallel summary calls |
|
|
Advanced: |
|
|
Advanced: extra search phrasings fused by reciprocal rank. Default stays one query |
|
|
Advanced: re-gather once when the excerpts are not enough |
|
|
Advanced: hop along on-disk citation edges inside the Ask scope. No network |
|
|
Advanced hop stays inside |
|
|
Advanced: |
|
|
Advanced: append the next stored chunk onto each hit |
Section extract (extract-sections)¶
Reads mirror PDFs and optional state/rag/text/ cache only. No Zotero writes.
Short bodies and table-of-contents noise are left in missing rather than
written as garbage. Re-run with --force to replace an existing
state/sections/<key>.json.
Key |
Default |
Meaning |
|---|---|---|
|
|
Default for |
|
|
Extra heading-synonym TOML ( |
|
|
MVP keys written under |
|
|
Include detected section headings ( |
|
|
Truncate each text field at this length |
Each populated MVP field is an object: text, provenance (rule, grobid,
or llm), optional heading, optional pages. Top-level source is pdf,
abstract, or none. Optional engine is grobid or rule. Schema:
paperful.sections.v1 (developer.md). When [grobid].enabled
(or --grobid), Paperful prefers TEI from the sidecar and falls back to
heading rules if GROBID is down or the PDF is a scan.
Timeouts, context budgets, prompt templates, tags, attachment hygiene, and browser-agent step caps: Advanced.
API keys stay in the environment (never in config.toml).
Exclude / protect collections¶
Hand-curated folders that paperful must not mutate. Top-level [[exclude]]
array (global safety policy — not a run profile).
[[exclude]]
path = "Hand curated"
mode = "full"
[[exclude]]
path = "Inbox/Staging"
mode = "add_only"
Key |
Meaning |
|---|---|
|
Match by collection path (preferred) or stable manager key. Subtree: |
|
|
|
Fine-grained overrides (explicit values win over the preset) |
|
Default |
|
Default |
Enforced on live library writes (Zotero / Mendeley). Denied ops raise a clear
excluded: … error. Reads, lint, export, and RAG are unchanged unless
skip_library_scope is on. paperful doctor lists active rules.
Advanced¶
Power-user knobs. Defaults are fine for a first library; change these when a run misbehaves or you host infrastructure yourself.
Fetch tuning¶
Key |
Default |
Meaning |
|---|---|---|
|
|
Title similarity below this marks a library DOI as suspect (eligible for in-memory swap). API failure is |
|
|
Title-similarity threshold for accepting a title→DOI match (Crossref, then OpenAlex, then Semantic Scholar) |
|
|
Parallel workers for open-access sources (Scholar, EZProxy, htmlpdf, SerpApi, and Sci-Hub are serial) |
|
|
Smaller downloads are rejected as error pages |
|
|
One-page density gate: soft-reject sparse stubs; hold denser one-pagers for attach |
|
|
Below this word count, a one-page PDF is treated as sparse (not a paper) |
|
|
Skip sources that look inapplicable from item metadata; use |
|
|
Captcha or block-page failures before a source pauses. A 429 does not count. After the pause, one item is tried again |
|
Chrome-like string |
HTTP |
|
built-in list |
Hostnames tried in order when Sci-Hub is opted in |
|
|
Random pause (seconds) before each serial vault / Sci-Hub / EZProxy / htmlpdf / Scholar page fetch. |
|
|
Network failures before a Sci-Hub mirror is skipped for the run |
LLM and attachment fine print¶
Table / key |
Default |
Role |
|---|---|---|
|
|
Allow non-loopback Ollama (LAN / explicit remote). Not required for Compose’s auto |
|
|
Per-completion timeout |
|
|
Cap on the Ollama context window for |
|
|
A |
|
|
Per-function LLM override for inbox match; empty uses |
|
|
Or path to a custom prompt file (relative to the config file); its SHA is stamped in the note footer |
|
|
Budget for PDF text sent to the model (head + headings + tail) |
|
|
Zotero child-note tag; re-runs update the note carrying it |
|
|
Report prompt, or a path relative to the config file. Wins over |
|
|
Bundled report shape: |
|
|
Budget for summary text in one model call (room left for the prompt) |
|
|
Base tag on the collection note. A second tag |
|
|
Per-completion timeout for the report |
|
|
Step and wall-clock caps for |
|
( |
Larger tool-capable model for browsing only; |
|
|
One retry with this tag before final |
|
|
Send page screenshots to the model; requires a vision-capable tag ( |
|
|
Pass |
|
|
Seconds allowed per PDF |
|
|
With |
|
|
With |
|
|
With |
|
|
With |
Twenty and SearXNG¶
Off unless you set them. Twenty is an open-source CRM (docs). SearXNG is a metasearch engine, the maintained fork of SearX; public instances are listed at searx.space. How Paperful uses them, including which public instances can answer, is snowball Advanced.
Key |
Default |
Meaning |
|---|---|---|
|
|
Talk to a Twenty workspace. Needs env |
|
|
When Twenty is ready, add CRM websites to a snowball |
|
|
Live People website lookups per run after the contact cache, for the late |
|
|
With |
|
|
Retries after HTTP 429 or 5xx ( |
|
|
Backoff base when the response has no |
|
|
Title of the note stamped on create and enrich |
|
|
Keyword added beside the collection slug |
|
|
Instance root, or |
OpenAlex API limits and snapshot store¶
Snowball and large fills talk to the live OpenAlex API
by default. A small crawl often needs no key; a free
OPENALEX_API_KEY (environment only)
raises the daily allowance. Full limits, resume behaviour, and Semantic Scholar
keys: snowball Advanced.
If you outgrow the API (heavy snowballs, shared VPN IP, institutional mirror), you can download the OpenAlex parquet snapshot yourself — locally or on a shared host — and point Paperful at it. Paperful does not ship the dump; most installs leave this unset.
When [openalex_store] is set, DOI, OpenAlex-id, and citing reads use the
live API first. The snapshot is the fallback when the daily budget is
spent. Citing (works_citing / citation-map expand) needs a prebuilt inverted
cites index — run paperful openalex-store build-cites, then set
cites_index_glob. The build is resumable: one works updated_date
partition at a time with .done/<date> markers (re-run after a crash skips
finished slices). Store-side cites-query / search= filtering is
title-only (display_name); abstract and fulltext stay on the live API.
Boolean OR / AND / NOT queries stay on the API. A fallback lookup that
misses timeout_s (default 120) counts as a failure; after fail_limit
(default 3) in one run, Paperful skips further snapshot calls. Keywords and
ORCID stay API-only. Citing with study_designs or listed_in also stays
API-only (older parquet may lack those fields).
When the live API daily budget is spent, DOI/id and citing (with index) keep
using the store; keyword / ORCID / full-text search still pause for
snowball resume.
Backends:
local_duckdb— DuckDB on this machine over a local parquet glob (needsduckdbonPATH, or setduckdb_bin).ssh_duckdb— DuckDB on a remote host; Paperful SSHs in, runs SQL, and copies small JSON rows back.
Env overrides: OPENALEX_STORE_SSH_HOST, OPENALEX_STORE_PARQUET_GLOB,
OPENALEX_STORE_CITES_INDEX_GLOB.
# Optional. Most users leave this unset.
# [openalex_store]
# backend = "local_duckdb" # or ssh_duckdb; later: http
# parquet_glob = "/data/openalex/data/works/**/*.parquet"
# cites_index_glob = "/data/openalex/cites/**/*.parquet"
# # duckdb_bin = "duckdb"
# # timeout_s = 120
# # fail_limit = 3 # skip the store after this many fallback errors in one run
# # build_timeout_s = 7200
# # snapshot_date = "2026-10-14" # date of the full snapshot you loaded
# # ssh_host = "nuc" # ssh_duckdb only
# # ssh_user = "you"
October 2026 topic change. OpenAlex moved works to new topic assignments
(keywords are now linked to topics). The change does not touch updated_date,
so an incremental sync of an older snapshot never picks it up. If you loaded
your snapshot before 14 Oct 2026, reload it fully from a newer release and
set snapshot_date; paperful doctor warns (amber) when snapshot_date is
before that day and notes when it is unset. Keyword hops against an old
snapshot return different neighbours than the live API. After the API-side
change, also run paperful snowball watch rebaseline --all --apply once so
reassigned works do not arrive as “new” (Snowball).