Configuration reference
This page is for administrators. It lists every setting Libris reads: the environment variables in .env, and
the settings an administrator can change at run time from Settings in the web interface. For each one you
will find its default, what it does and when you would change it.
If you are installing Libris for the first time, you do not need any of this yet: scripts/setup.py (run by the
installer) writes a working .env. Start with the Docker guide and come back here when you want to
open Libris to your network, tune limits or change a default.
How configuration works
-
.envis the only file you edit. It sits next todocker-compose.yml, is created bypython3 scripts/setup.pyfrom.env.example, and is read by theapi,workerandmigratecontainers. Keep it private (mode0600) and never commit it. -
Names are case-insensitive, but this page uses the upper-case form found in
.env.example. A variable that is absent from.envtakes the default shown here. -
Apply a change by recreating the containers:
docker compose up -d --no-build --waitEvery container reads the environment only when it starts. Run the same command with
--profile codexif you use the Codex bridge. -
Invalid values stop the application. Each numeric setting has an allowed range (shown below). A value out of range, a
SECRET_KEYshorter than 32 characters, or aMETRICS_TOKEN/API_WEBHOOK_SECRETthat is set but too short prevents the API and the worker from starting;docker compose logs apinames the setting. -
Some settings can be overridden in the interface. Autopilot, budgets, webhooks, automatic recovery, the fair queue, SMTP mail, OpenViking memory and its cleanup, and SearXNG search have a page under Settings. There, a saved value wins over the environment. See Settings changed in the interface.
Installation and network
These variables are read by Docker Compose and the web server rather than by the application code.
| Variable | Default | What it does | When to change it |
|---|---|---|---|
LIBRIS_IMAGE | registry.libris-translate.com/libris/libris:latest in the source Compose file; an immutable digest in a customer installation | Application image used by api, worker and migrate. The registry bootstrap resolves the selected tag and pins its immutable digest. | Normally, rerun the bootstrap with LIBRIS_TAG; edit this only for a deliberate local build such as epub-translator:local. |
LIBRIS_CODEX_IMAGE | registry.libris-translate.com/libris/libris:codex-latest in the source Compose file; a matching immutable digest in a customer installation | Image of the optional Codex bridge. The bootstrap verifies that its version and revision match the application before pinning it. | Rarely; only if you deliberately select a matching bridge image yourself. |
LIBRIS_CONTAINER_PREFIX | libris | Stable operator-facing container names: libris-database, libris-migrate, libris-api, libris-worker and, with its profile, libris-codex. It does not name the volumes. | Only for a disposable stack that must coexist on the same Docker host; CI and audit scripts set a unique prefix themselves. |
LIBRIS_PROJECT | libris (written into .env by the installer) | Compose project name, which prefixes the network and the volumes (<project>_database, <project>_books). Installations made by earlier releases ran as epub-translator; the installer renames them once (Docker). | Never on an existing installation: a new name means new, empty volumes. On a production host, librisctl migrate-project renames it and copies the data. |
LIBRIS_ENV_FILE | .env | File passed to the containers as their environment. | To keep the configuration outside the repository, e.g. LIBRIS_ENV_FILE=/etc/libris/libris.env docker compose --env-file /etc/libris/libris.env up -d. |
POSTGRES_PASSWORD | generated | Password of the translator PostgreSQL role. Required: Compose refuses to start without it. | Never after the first start. Changing it in .env does not change the password already stored in the database volume. |
BIND_ADDRESS | 127.0.0.1 | Host interface on which the web port is published. | 0.0.0.0 to reach Libris from other machines on a private network. Keep 127.0.0.1 behind a reverse proxy on the same host. |
PORT | 8088 | Host port of the web interface and API. | When 8088 is already taken. Update ALLOWED_ORIGINS to match. |
FORWARDED_ALLOW_IPS | unset (only 127.0.0.1 is trusted) | Addresses of reverse proxies whose X-Forwarded-For / X-Forwarded-Proto headers are trusted, comma-separated. Read by the web server (Uvicorn). | Behind a reverse proxy: set it to the proxy’s address as seen from the container (for example the Docker bridge gateway 172.18.0.1) so logs and the failed-login throttle and the password-reset limit (10 requests per client in 15 minutes) see real client addresses (each refusal of that limit is logged as a warning with bucket=<address>: the proxy’s own address there means this variable is missing); never *, which lets anyone forge the address. |
Compose also sets DATABASE_URL, DATA_DIR=/data and TMPDIR=/data/tmp for the application containers; do not
put them in .env for a Docker installation.
Security and access
| Variable | Default | What it does | When to change it |
|---|---|---|---|
SECRET_KEY | generated (required, at least 32 characters) | Encrypts provider API keys, the OpenViking key and webhook secrets stored in the database. | Never. Keep it with your backups. If it changes, stored keys can no longer be read: Libris asks you to enter each provider key again and ignores unreadable saved secrets. |
BOOTSTRAP_USERNAME | admin | Name of the first administrator account. | Before the first start, if you want another name. |
BOOTSTRAP_PASSWORD | generated (at least 12 characters) | Password of the first administrator. Used only when the database has no account at all; the API refuses to start on an empty database without it. | Before the first start. Afterwards, change passwords in the interface: editing .env does not touch existing accounts. |
ALLOWED_ORIGINS | http://localhost:8088,http://127.0.0.1:8088 | Browser origins (scheme, host and port, no path) allowed to send changes. Requests with any other Origin get 403. Comma-separated; spaces around each origin are ignored. | Whenever users reach Libris through another address: http://your-server:8088 on a LAN, https://books.example.com behind HTTPS. It is an origin check, not a firewall. Changing it takes effect once the containers are recreated (librisctl up): a restart keeps the environment they were created with. |
COOKIE_SECURE | true | Marks the session cookie Secure: browsers send it only over HTTPS, and to http://localhost / http://127.0.0.1 on the Libris machine itself, which current browsers treat as secure. | Keep true behind HTTPS and for local use. Set false only when users open Libris over plain HTTP on a network address (http://192.168.1.10:8088, http://your-server:8088): there the browser drops a Secure cookie and sign-in seems to do nothing. |
SESSION_DURATION_HOURS | 24 (1–2160) | Lifetime of a sign-in session. | Longer for a private single-user instance, shorter on shared machines. |
WEBAUTHN_RP_ID | empty (the host of the first ALLOWED_ORIGINS entry) | The domain a security key is bound to. A key registered under one domain is refused under every other: that is what makes it unphishable. | When Libris answers at several addresses, so that keys keep working: pin the one people sign in through. |
WEBAUTHN_RP_NAME | Libris Translate | The name an authenticator shows when it is asked to create a key. | To name the installation rather than the product. |
OPENAPI_ENABLED | true | Serves the API description at /openapi.json, to signed-in users only. | false if you do not want the API schema exposed at all. |
PUBLIC_HEALTH_DETAILS | false | /health always gives its status, the version and the configuration state; the list of checks behind that state, and the state of OpenViking and of its send queue, go to a signed-in administrator only. true gives them to everyone. | For a monitoring probe that has no session, on an address only it can reach. |
METRICS_TOKEN | empty (endpoint disabled) | Enables GET /metrics for Prometheus. Scrapers must send Authorization: Bearer <token>. At least 24 characters when set. | To monitor Libris; generate one with openssl rand -hex 32. See operations. |
Single sign-on has no environment variable either: an administrator gives the issuer, the
client id and the secret in Réglages › Authentification unique, and Libris discovers the rest from
{issuer}/.well-known/openid-configuration. The secret is encrypted with SECRET_KEY like every
other secret; the address to declare at the provider is shown on the same screen and is built from
the first ALLOWED_ORIGINS entry. An account opened by a provider has no Libris password and no
Libris second step — both belong to the provider.
LDAP / Active Directory has no environment variable either: an administrator configures it in Paramètres › Authentification unique, card Annuaire LDAP / Active Directory, and the sign-in screen then offers a choice between the directory and a local Libris account. The step-by-step setup for OpenLDAP and Active Directory is in ldap.md. The settings are:
| Setting | Default | What it does |
|---|---|---|
| Directory type | OpenLDAP | Chooses the defaults of the four attributes and of the user filter below: uid / mail / entryUUID / memberOf for OpenLDAP, sAMAccountName / mail / objectGUID / memberOf for Active Directory. |
| Directory address | empty | ldaps://host:636 (recommended) or ldap://host:389. No path, no query. Use a host name that is in the server certificate: an IP address is refused by the host name check. |
| StartTLS | off | Upgrades an ldap:// connection to TLS before anything is sent. Refused with ldaps://, which is already encrypted. |
Allow plain ldap:// | off | Without StartTLS, ldap:// sends every password in clear on the network, so it is refused unless this is ticked explicitly. Only for a directory on the same host or a private link. |
| Authority certificate | empty (system trust store) | PEM of the CA that signed the directory’s certificate, pasted into the form. Certificate and host name verification is always on; there is no setting that turns it off. |
| Service account DN / password | empty | The account Libris searches the directory with. The password is encrypted with SECRET_KEY, never sent back to the browser, never logged; leave the field empty to keep the stored one. An empty DN means an anonymous search, when the directory allows it. |
| Search base | empty (required) | Where people are looked up, e.g. ou=people,dc=example,dc=com. |
| User filter | depends on the type | LDAP filter with {username} where the login typed goes, escaped (*, (, ), \, NUL). OpenLDAP: (&(objectClass=inetOrgPerson)(uid={username})). Active Directory: sAMAccountName or userPrincipalName, disabled accounts excluded. Exactly one entry must match. |
| Name / email / stable identifier / group attributes | depends on the type | Where the Libris username, the email and the group memberships are read, and the attribute that identifies an entry for good. Accounts are matched on the stable identifier, never on a name or an address. |
| Allowed groups | empty (everyone the filter finds) | Group DNs, one per line: only their members (read from the group attribute, compared case-insensitively) may sign in. |
| Administrator group | empty (roles set in Libris) | When set, its members are Libris administrators and the others are not, updated at each sign-in; the role can then no longer be changed in Libris. The last active administrator is never demoted this way. Members of this group are also allowed in. |
| Create accounts on first sign-in | off | As for SSO: without it, a directory person is refused until an administrator links an account (Paramètres › Utilisateurs › Rattacher à l’annuaire); with it, each new person takes a licence seat. |
| Timeout | 10 s (1–60) | Connection, response and search time limit. |
| Name shown on the sign-in screen | Annuaire de l’entreprise | The label of the directory choice. |
A directory account has no Libris password (password change, Mot de passe oublié, reset and local rename are refused), but keeps Libris’ second step: a directory only checks a password, so codes and security keys are offered and required as for a local account, and a recent proof (#102) is given with the directory password. Referrals are never followed, and failed directory sign-ins count in the same throttle as local ones for the same name.
There is no setting for the second step of a sign-in: each account turns it on for itself in
Mon compte › Double authentification: a six-digit code from an authenticator application, a
security key (WebAuthn), or both, plus ten single-use recovery codes. A key needs a secure
context — HTTPS, or localhost — so an installation reached over plain HTTP on a LAN offers the
authenticator only. An administrator may remove it from an account that lost its telephone —
Réglages › Utilisateurs — which is written in the audit trail; nobody can turn it on in somebody
else’s place. The automation API is untouched: a token is a secret of its own.
Sensitive account changes require a proof less than five minutes old, bound to the
current browser session. This interval is deliberately not configurable and does not
extend SESSION_DURATION_HOURS. A complete local sign-in counts immediately; WebAuthn
must actually verify the user (PIN or biometrics), rather than just a touch. Existing
sessions remain usable after an upgrade, but carry no fresh proof until confirmed.
For SSO administrators, qualify the identity provider before relying on account management:
it must support a new authorization with prompt=login, max_age=0, and a signed, recent
auth_time for the original issuer and subject. A normal SSO sign-in is not enough. The
confirmation uses the same registered callback URL, fresh state/nonce and PKCE; the callback
does not open a session and works without the SameSite=Strict cookie. The original window
finishes the check with its own cookie. Test popups and the provider’s opener-isolation policy
in the browsers used by administrators. Missing proof is refused without a silent-login loop
or a fallback local password for an SSO identity; the account form keeps its draft.
Stop the API before applying or reverting this schema change. On downgrade, ordinary sessions and pending ordinary SSO sign-ins remain, but in-flight confirmation requests are removed: an older binary must never treat an account confirmation as a new login. Upgrading again does not resurrect proof timestamps. This guard covers account mutations and the personal recovery email, not every provider, SMTP or licence setting.
DEMO_MODE
| Variable | Default | What it does | When to change it |
|---|---|---|---|
DEMO_MODE | false | Makes the instance read-only for a public demonstration. Only GET, HEAD, OPTIONS, sign-in/out, automatic demo sign-in and grouped EPUB/text exports are allowed; other writes return 403 demo_read_only. /mcp, SSO and LDAP are closed; the worker only sends licence heartbeats; /health answers demo: true. | To run a public demo after loading its showcase books. |
DEMO_USERNAME | empty | Name of the dedicated local account visitors enter through automatic demo sign-in. The account must be active, non-admin, local and have two-factor authentication disabled. | Set together with DEMO_MODE=true for a public demo. Never publish the account password. |
Storage and paths
The defaults match the supplied image. Change them only when you run the backend outside Docker.
Sizing the connection pool. Every passage in flight renews its job’s lease, writes a checkpoint
and reads its context, so it holds a connection for a moment, again and again. How many passages may
be in flight is the sum of the providers’ Livres simultanés (max_concurrency) — not the
largest of them, because books running on different providers add up — plus the loops that never
stop: heartbeats, the outbox, mail, the licence. A pool smaller than that does not fail loudly:
callers queue, wait DB_POOL_TIMEOUT, and are answered with an error that reads exactly like a
database that is down.
Both processes say at start-up whether the pool covers the providers: db_pool=ok capacity=… providers=…
in the log, or db_pool=undersized with what to raise. A log line reading status=pool_exhausted
says the pool ran out; status=database_unavailable says the database itself did not answer. The
defaults (40 + 40 per process) cover a busy installation with room to spare. Raising them means
raising max_connections on the database service too — the supplied Compose file sets it to 200
for exactly this reason; otherwise the server becomes the next thing to run out.
| Variable | Default | What it does |
|---|---|---|
DATABASE_URL | sqlite:////data/app.db (Compose sets PostgreSQL) | SQLAlchemy database address. Docker installations always use the bundled PostgreSQL. |
DB_POOL_SIZE | 40 (5–500) | Connections one process keeps open on the database. |
DB_POOL_MAX_OVERFLOW | 40 (0–500) | Connections it may open on top of those in a burst, and close again afterwards. |
DB_POOL_TIMEOUT | 5 (1–120) | Seconds a caller waits for a free connection before giving up. Short on purpose: a job that waits minutes for a connection has already lost its lease. |
DATA_DIR | /data | Root of the books volume: books/, sources/, projects/, exports/, staging/, results/ and tmp/ are created in it at start-up. |
EPUBCHECK_JAR | set by the image to the bundled EPUBCheck | Path of the EPUBCheck jar. Empty: exports are checked by Libris’ internal checks only, and reports say EPUBCheck is not configured. |
FRONTEND_DIR | /app/frontend/dist | Built web interface served by the API. |
PROMPT_DIR | /app/prompts | Directory of the built-in prompt templates. Edited prompts are stored in the database, not here. |
Journal
Every service of the application (API, worker, migrations) writes its events to a file of its own in
LOG_DIR, besides the usual container output; the production deployment script writes deploy.log there too.
librisctl journal, or python -m app.journal inside a container, reads them as one chronology — see
operations. The Compose file mounts LOG_DIR from the logs volume, or from the host
directory named by LIBRIS_LOG_DIR (owned by uid 10001, mode 0700); the production scripts use
$LIBRIS_PRODUCTION_BASE/logs (or LIBRIS_PRODUCTION_LOG_DIR).
| Variable | Default | What it does |
|---|---|---|
LOG_FILE_ENABLED | true | Writes the journal files. false: the container output only. |
LOG_DIR | /logs | Directory of the journal inside the containers. |
LOG_LEVEL | INFO | DEBUG, INFO, WARNING or ERROR: the least serious event written, to the file and to the container output. |
LOG_MAX_MB | 10 (1–1024) | Size at which a file is rotated. |
LOG_BACKUPS | 5 (1–100) | Rotated files kept per process: a service never takes more than LOG_MAX_MB × (LOG_BACKUPS + 1). |
LOG_RETENTION_DAYS | 14 (1–3650) | Files older than this are removed, at start-up and at each rotation — never the file a running process writes. |
LOG_TIMEZONE | UTC | IANA zone the times are written in (Europe/Paris). Every line carries its UTC offset, so files written in different zones still merge in order. An unknown zone falls back to UTC, and says so. |
Uploads and imports
| Variable | Default | What it does | When to change it |
|---|---|---|---|
MAX_UPLOAD_MB | 256 (1–4096) | Largest request body from the web interface: one uploaded EPUB, text file or project archive. An import holds the file and its unpacked content in memory: count about MAX_UPLOAD_MB + the unpacked size per upload in progress. | For even larger books or archives, or lower on a host short of memory. Your reverse proxy must accept the same size. |
MAX_UNPACKED_MB | 1024 (1–16384) | Largest total unpacked size of an EPUB or project archive. | With MAX_UPLOAD_MB, for large illustrated books. |
MAX_ENTRIES | 5000 (1–100000) | Most files inside one EPUB or project archive. | Rarely; for books made of thousands of small files. |
MAX_COMPRESSION_RATIO | 100 (10–100000) | An EPUB that unpacks to more than 8 MiB with a higher overall compression ratio is refused as a possible zip bomb. | Only if a legitimate book is refused for this reason. |
IMPORT_MAX_FILES | 500 (1–5000) | Files in one guided import. | To import a very long series of chapter files at once. |
IMPORT_MAX_SESSION_MB | 2048 | Total size of the files of one guided import. | With IMPORT_MAX_FILES. |
IMPORT_SESSION_HOURS | 24 (1–720) | How long uploaded files wait for confirmation in DATA_DIR/staging before they are deleted. | If people leave imports unconfirmed for longer. |
IMPORT_CONFIRM_LOW_CONFIDENCE | false | false: when a volume or chapter number is guessed with low confidence, the best guess is kept and the reason recorded. true: the import assistant asks a person to confirm it. | true if you prefer to check every uncertain numbering by hand. |
TEXT_CHAPTER_MAX_CHARS | 2000000 (1000–50000000) | Longest single text chapter (TXT file or JSON chapter), in characters after decoding. | Rarely; for whole books delivered as a single chapter. |
A project archive that would exceed MAX_UPLOAD_MB or MAX_UNPACKED_MB is refused at export time, with the
setting to raise, because it could not be imported again.
Translation pipeline
| Variable | Default | What it does | When to change it |
|---|---|---|---|
PASSAGE_MAX_CHARS | 3500 (500–20000) | Longest passage, the unit of text sent in one model call. Applies to volumes and chapters imported afterwards; existing books keep their cut. A volume can set its own. | Longer passages spread the fixed context of each call over more text and lower the cost; see cost control before going beyond 8000. |
REVIEW_MODE | separate | At high and maximum quality: separate reviews a passage, then revises it in a second call when the review finds something; fused reviews and corrects in one call. A volume can choose its own. | fused to save calls, once you have checked the quality on your books. |
FINAL_REVIEW_ENABLED | true | Runs the final AI review of whole-book translations and automation requests. | false to skip that stage everywhere (cheaper, less thorough). A single API request can also leave it out. |
WORKER_BOOK_PARALLELISM | 0 (0–16) | Passages of one book analysed (parallel mode), translated or reviewed at the same time. 0 follows the provider’s Concurrent books capacity, shared by the books using it; 1 processes one passage at a time. A volume, a launch or an API request may ask for fewer (threads). | 1 for providers that struggle with parallel calls; a small number to leave capacity to other books. |
WORKER_PROCESSES | 1 (1–16) | Processes running the books in the worker container. 1: the worker itself. Above: as many job processes started and supervised by the worker, which keeps the periodic loops; one book alone still uses one core, and each process has its own database pool (operations). | |
ANALYSIS_MODE | parallel | How a volume is analysed before translation. parallel: every passage is analysed on its own, several at once, then reconciled with what the passages before it established; strict: one passage after the other. A volume (Analysis mode in its settings), a launch or an API request may choose the other. See architecture. | strict only to compare, or for a provider that allows a single call at a time (the parallel mode makes about 1.8 times more analysis calls). |
ANALYSIS_RECONCILIATION | all | Parallel mode: all reconciles every passage with what precedes it; flagged only the passages whose own analysis left something to resolve (nobody named, an unresolved reference, a short form or a new name close to a known one). | Keep all: flagged saves calls but lost part of the alias resolution in the evaluation. |
ANALYSIS_MIN_COVERAGE | 1 (0–1) | Share of the passages of a volume that must hold an analysis in memory before the translation stage starts, once the passages given up were asked again. At 1, every section must also be consolidated into the Book Bible. Under it the job stops as blocked with stop_reason = analysis_incomplete; with no analysis at all, or an empty Book Bible nobody validated, with analysis_unusable — instead of translating with an incomplete memory of the book (#161). | Leave it at 1: a passage translated without its analysis breaks the consistency of the glossary, the characters and the memory. Lower it only to accept, knowingly, translating a book some passages of which no provider will analyse; a job stopped this way is resumed once the provider answers properly: what was given up is asked again and a bible left empty is rebuilt. |
WORKER_HEARTBEAT_SECONDS | 2 (1–20) | How often a running job renews its 60-second lease and checks for pause or cancel. | Normally never. |
PROVIDER_RECOVERY_BASE_SECONDS | 60 (5–3600) | First wait before retrying a provider that is unavailable (network error, timeout, HTTP 429 or 5xx). Later waits double. | Replaced by the Automatic recovery delay while one is saved in the interface. |
PROVIDER_RECOVERY_MAX_SECONDS | 3600 (5–86400) | Longest wait between two retries. A provider’s Retry-After can lengthen it, up to 24 hours. | Lower it to retry more often during long outages. |
PROVIDER_MEMBER_PRIVATE_NETWORKS | empty | A provider a member added for themselves (Settings › LLM providers › Members’ providers) only reaches public addresses: a name that resolves to a loopback, private, link-local, shared (CGNAT) or otherwise reserved address — IPv4 or IPv6, IPv4-mapped and NAT64/6to4 forms included — is refused when the provider is saved and at every connection (test, translation, retries, batch submission, polling and results). The name is resolved for each request and the connection goes to the address checked, the certificate still verified against the name; redirects are not followed and proxy variables are ignored. These networks (CIDRs, comma-separated, e.g. 192.168.1.40/32) are opened to members anyway. The installation’s own providers, set up by an administrator, are not concerned: a vLLM or Ollama on the LAN keeps working. | Leave it empty unless members must reach a local model; open the narrowest network (one host: /32), or better, add that model as an installation provider shared with members. |
PROVIDER_RESPONSE_MAX_MB | 8 (1–1024) | Largest body, once decompressed, accepted from a provider for one ordinary call (completion, model list, error page). Past it the connection is closed before the JSON is parsed and the request is recorded as failed. Batch results keep their own fixed ceilings. | Raise it only for a model whose legitimate answers are larger; a completion is normally well under 1 MB. |
Autopilot
The autopilot takes a launched book from import to export without a human step; see autopilot. These variables are defaults that an administrator can override in Settings › Autopilot.
| Variable | Default | What it does |
|---|---|---|
AUTOPILOT_ENABLED | true | Default for new whole-book launches, from the interface and the API. A volume’s own autopilot setting, or "autopilot": false in a launch, overrides it. |
AUTOPILOT_MAX_ROUNDS | 3 (1–10) | Rounds of recovery, final review and AI arbitration before the remaining open points are settled. |
AUTOPILOT_FALLBACK_PROVIDERS | empty | Providers (names or ids, comma-separated) tried in order when the job’s provider is down or fails a passage, after the volume’s own fallback providers. |
AUTOPILOT_ESCALATION_PROVIDER | empty | The stronger model (name or id), kept for the hard cases: a passage that comes back to the AI arbitration because its points resisted the first one, a passage no rung of the recovery ladder could translate, and a passage the book’s model failed five times (two calls on the stronger model, then back). No step of the book moves to it. A book or a series may name its own in Settings › Autopilot › Stronger model. Empty: those passages stay on the book’s model. (AUTOPILOT_ESCALATE_AFTER and AUTOPILOT_ESCALATE_WINDOW no longer exist and are ignored.) |
AUTOPILOT_OUTAGE_MAX_RETRIES | 5 (1–100) | Waits for an unavailable provider before switching to the next fallback. With none left, the job ends failed. |
AUTOPILOT_OUTAGE_MAX_WAIT_SECONDS | 3600 (0–604800) | Longest total wait for an unavailable provider before switching. |
AUTOPILOT_GLOSSARY_MIN_CONFIDENCE | 0.75 (0–1) | How often the book’s narrative text must use a proposed term (as whole words, headings and table of contents excluded) for the proposal to be accepted automatically: none gives 0, once 0.6, twice 0.8, three times or more 1. At 0.75 a term must be used at least twice. It measures the frequency of the source term, not the quality of the translation proposed. |
AUTOPILOT_IDENTITY_MIN_CONFIDENCE | 0.8 (0–1) | Confidence a proposed series identity link (same character across volumes) needs; below it the proposal is rejected. |
AUTOPILOT_BIBLE_MIN_COVERAGE | 0.8 (0–1) | Coverage a Book Bible update needs to replace the current one; below it the Bible is left as it is. |
AUTOPILOT_STALE_MIN_COVERAGE | 0.5 (0–1) | Coverage a refreshed chapter context needs to replace an outdated one; below it the old context is kept. |
AUTOPILOT_STYLE_MIN_CONFIDENCE | 0.8 (0–1) | Confidence a style sheet value proposed from the book at the end of its analysis needs to be written into an open field of the volume’s sheet; below it the proposal waits for a person in the style sheet screen. A field a person or the series decided is never written over. See autopilot. |
Change the thresholds when the autopilot accepts too much (raise them) or leaves too many decisions unmade (lower them).
Administrators can configure the transport in Settings → Email delivery without shell access
or restarting the API and worker. Saved settings override the SMTP_* environment values below.
The explicit off switch stops both queuing and sending; already queued messages remain in the outbox.
Restore SMTP environment settings removes the override and applies the environment again.
The password is write-only and encrypted with the existing SECRET_KEY. Leave its field empty to
keep it, or tick the explicit clear option to remove it. Back up SECRET_KEY along with the database;
after a key change, enter the SMTP password again. TLS certificate verification cannot be disabled.
Queue a test email writes to the same outbox used by notifications and password recovery. A 202 answer means queued, not delivered: the worker must be running. Refresh the outbox to see pending, sending, sent and failed messages, attempts, retry time and a safe explanation. Neither the body, reset token, context nor raw server reply is returned. A repaired failed message can be queued again. Password reset messages cannot be retried here: request a fresh reset link instead of resending an expired one.
Multiple workers reserve each message atomically before delivery. A two-minute lease is renewed
every forty seconds while a slow send is in progress; no SQL session is held during SMTP. A worker
crash leaves a recoverable reservation, and eight interrupted attempts become a visible failure.
Administrative retry resets the attempt budget but not ownership: a new opaque token fences out
the old sender. No retry is allowed while sending. Delivery is at least once, not exactly once:
SMTP acceptance followed by a crash before the local acknowledgement can cause redelivery.
Retries retain their Message-ID, without assuming that the recipient deduplicates messages.
Stop old-version workers before upgrading: they do not recognize sending. Stop all mail
workers before downgrading. The downgrade returns in-flight messages to the pending queue
(or failed if their attempt budget was exhausted) before removing lease ownership; it deletes
no mail. A message already accepted by SMTP can consequently be sent again after rollback.
Attachments are checked again against DELIVERY_MAX_MB immediately before MIME encoding, since a
book can grow after it was queued. This check avoids expanding an oversized payload into base64; it
does not itself bound the EPUB builder’s memory usage. Oversized attachments are visible as failures.
Libris tells a reader four things, and only four: a book is finished, a job stopped and is waiting for them, the licence’s word quota is running out, and the licence could not be renewed. Everything else it does in silence. A message is sent when something ended or is about to stop, never when something merely began — nobody wants a mail per passage, everybody wants one when the book is done.
Silence is the default. An account with no address receives nothing, and an installation
upgrading to this version starts silent: a reader has to give an address, in My account ›
Notifications, and may turn off each kind and choose French or English. Nothing is sent from inside
a request either: a message is written to email_outbox in the same transaction as the thing it
announces, and the worker drains the queue, waiting a minute, then two, then four, up to six hours,
and giving up after eight attempts.
Submission is authenticated to a real mail server. An anonymous relay is refused by half the world, and a notification that lands in a spam folder is a notification that was not sent.
| Variable | Default | What it does |
|---|---|---|
SMTP_HOST | (empty) | The mail server. Empty: nothing is queued and nothing is sent, which is what a workstation wants. |
SMTP_PORT | 465 | 465 is implicit TLS; 587 needs SMTP_STARTTLS=true. |
SMTP_STARTTLS | false | Upgrade a plain connection instead of starting encrypted. |
SMTP_USERNAME, SMTP_PASSWORD | (empty) | The mailbox that sends. Without a username nothing authenticates, which most servers refuse. |
SMTP_SENDER | (empty) | Libris <notifications@example.com>, or just the address. Empty counts as no mail configured. |
SMTP_TIMEOUT | 20 (1–300) | Seconds one delivery may take. |
PUBLIC_URL | (empty) | The address messages point readers back to. Empty: they carry no link. |
DELIVERY_MAX_MB | 20 (1–100) | A finished book above this is announced with its address instead of attached: mail servers refuse large attachments, and a refusal delivers nothing. |
SOURCE_WATCH_HOSTS | (empty) | Hosts a volume may follow a feed on, comma separated (*.example.org allowed). Empty: no source is watched at all. An address that resolves to a private network is refused, like a webhook’s. |
DELIVERY_DIR | (empty) | Default publication folder, relative to DATA_DIR unless absolute. The worker writes completed books through a durable retry queue. Installation settings can override or disable it. Calibre-Web requires a separate import step, not a copy beside its database. |
Both TLS modes verify the server certificate and its host name before authentication or mail is sent. For a private certification authority, install its trust chain in the container’s trusted certificate store. A self-signed, expired or mismatched certificate is an error to correct at the mail server or in the trust store, not a reason to disable verification. A failed STARTTLS upgrade never falls back to sending credentials in clear text.
Where a reader’s own finished books are sent is set per account, in My account › Notifications: an address of its own, because a Kindle address is not a mailbox anyone reads. Setting it is the only opt-in there is — there is no kind to turn off, and an empty address sends nothing.
Licence
A licence sells accounts and machines. Accounts are the people who may sign into the
installation. Creating an account past that number is refused — and only that: signing in, working,
reading and exporting are never touched, because a licence that shrinks must not lock anyone out of
the installation they are working in. Paramètres › Utilisateurs (Settings › Users) shows how many seats are held of how
many. Machines are the installations the key may be activated on at once: 2 for the trial, 3 for
Personal, 5 for Studio, 10 for Pro. When they are all taken, activating one more releases the oldest,
provided it has held its place for at least 7 days; otherwise the activation is refused
(move_too_soon). Release this machine in Settings › Licence frees a place at once — the thing to
do before a move.
The words of the quota cycle — or of the calendar month with a licence server that predates cycles —
reach the licence server with the heartbeat every LICENCE_HEARTBEAT_HOURS, and
now also when a job ends — one call per job, never per passage, so the number an editor reads is
at worst one book behind rather than an hour.
The quota renews on the subscription’s anniversary, not on the first of the month: a licence sold on the 12th starts a new quota on the 12th of every month (one begun on the 31st renews on the last day of shorter months). The licence server names the cycle in the certificate — its bounds, the plan’s allocation, and what the previous cycle overran, which it takes from this one, once. Libris counts its words in that cycle, reports the last words of an ended cycle to that cycle, and shows the date of the next quota in Settings › Licence. With a licence server that predates cycles, nothing changes: the calendar month, no carry-over.
A book is counted as soon as it is added, not as it is translated: all of its words, at the import,
even if it is deleted afterwards without being translated. This holds for every road (import assistant,
automation API, MCP, source watches, archive restore, second language) and for every chapter added or
replaced later (only its new passages). A book is accepted while its words fit in what the cycle has left
plus the margin LICENCE_QUOTA_OVERRUN_WORDS; beyond, it is refused whole (402 licence_quota_insufficient) and nothing is counted. Translating it, launching it again or redoing a passage
then costs nothing more, even once the quota is reached; only a provider comparison, which calls the models
on top, is still counted apart. Books added before this rule (0.16.4 and earlier) keep being counted as
before, passage by passage as they are translated: nothing is counted again at the upgrade, the counter goes
on from the number it had.
Libris is licensed software. An installation activates once with the key its reader bought, in
Settings › Licence, and from then on runs on a certificate the licence server signs and renews
every LICENCE_HEARTBEAT_HOURS (every hour by default).
The certificate is what decides — not a setting and not the reply that carried it — and it is worth
three days (72 hours, set by the licence server), so an outage at the licence server never interrupts anyone’s reading; a revocation stops
the installation at the next renewal it manages to make. What is refused without a valid licence is
the work that costs words — a launch, and every model call of a job already running, which pauses with
stop_reason = licence and resumes once the licence allows work again. Everything already produced stays
readable, exportable and deletable: a reader locked out never loses a book.
A revocation is the only kind of answer that takes the certificate away. Four refusals stop an installation — the licence is revoked, suspended, expired, or the server does not know the key — and anything else the licence server may answer, including a request it did not understand, counts as an outage: the certificate in hand keeps its three days, the reason is shown in Settings › Licence, and the next renewal tries again. A version of Libris newer than the licence server it calls must never be able to stop a licensed reader.
What this installation signs. At its first activation Libris draws an Ed25519 pair, keeps the
private half encrypted with SECRET_KEY alongside the licence key, and sends the public half. Every
report afterwards carries a counter, this machine’s clock and a signature over the whole report, so
that what the licence server records cannot be forged by a third party nor replayed. Nothing to set
up: an installation that holds no pair yet enrols one the next time it activates, and one whose key
the server does not know activates again on its own rather than waiting for somebody. A reader who
modifies their own copy can of course read that key out of their own database — this makes a report
trustworthy against a third party, not against its owner.
The fingerprint sent to the licence server has two halves: a hash of the host’s machine-id, which
changes when the reader changes machine and survives a redeployment, and an identifier Libris drew at
its first start, which follows a restored backup. A container must be given the host’s machine-id —
/etc/machine-id:/etc/machine-id:ro, as the Compose file does — or every recreation looks like a
move to another machine and Libris falls back on a weaker fingerprint of its own.
| Variable | Default | What it does |
|---|---|---|
LICENCE_SERVER_URL | https://sub.libris-translate.com | Where the installation activates and renews. It may be changed: a certificate is only accepted when the release key signed it, so another server grants nothing. |
LICENCE_HEARTBEAT_HOURS | 1 (1–48) | Hours between two renewals, made by the worker alone so that an installation counts as one wherever its API runs. Keep it well below the three days a certificate lasts. An hour keeps what the licence server shows close to what is actually happening; the grace an outage leaves you is the certificate’s own life, 72 hours set by the licence server, and does not move with it. |
LICENCE_QUOTA_OVERRUN_WORDS | 20000 (0–1000000) | The margin past the quota of the period (the licence’s quota cycle, anchored on the subscription date, or the calendar month with an older licence server): a book is added while its words fit in what is left plus this margin, and its words are counted at once. A book already counted is translated even past the quota; a book added before words were counted at the addition is finished within this margin rather than left in half. What is overrun is taken from the next cycle’s quota by the licence server. |
What a plan grants
The certificate carries what the licence’s plan grants, and Libris applies it as it stands in the certificate, never from a setting:
| In the certificate | What Libris does with it |
|---|---|
accounts | Creating an active account beyond this number is refused; existing accounts keep working. |
instances (machines) | Enforced by the licence server at activation. When every machine of the plan is taken, activating one more releases the oldest, provided it has held its place for at least seven days; otherwise the activation is refused (move_too_soon). Release this machine in Settings › Licence frees a place at once. |
automation_api | API tokens, /api/v1 and the MCP server (/mcp). Without it they answer 402 automation_not_licensed; existing tokens are kept, suspended, and work again once the licence grants it. |
sharing | Inviting members to a book and review links. Without it, a new member or link is refused with 402 sharing_not_licensed; members already invited keep their access, existing review links are suspended without being deleted. |
quota | The words of the cycle (see above); 0 means no limit. |
Settings › Licence shows the plan, the words of the cycle and, for each right, whether it is included. The automation API and sharing come with the Studio and Pro plans, not with the trial nor the Personal plan. A certificate from a licence server that predates these rights does not name them, and keeps what it always allowed. The words, machines, accounts and prices of each plan, and the trial, are on libris-translate.com: the licence server grants them, the website publishes them, and this documentation does not repeat the numbers.
Perpetual certificate (end of the publisher’s activity)
If the publisher of Libris ends its activity, the licence server stops. Before it does, every valid
licence receives by email a perpetual certificate (dev/libris-licence#29): signed with the same key
as the hourly one, with no end date, no machine, and no word limit
(quota.words = 0). It is bound to the licence key instead: its key_sha256 is the SHA-256 of the
key as the licence server writes it (LIB-XXXX-XXXX-XXXX-XXXX), so the certificate alone is worth
nothing, and with the key it works on any installation — a reinstallation, a new machine or a
restored backup included.
Install it once, as an administrator:
- Settings › Licence › Perpetual certificate: paste the certificate (the line breaks and indentation
of the letter do not matter) or choose the
.libris-certificatefile, and give the licence key unless the installation already holds it — in any case, with or without dashes; - or from a shell, in the API container:
docker compose exec -T api python -m app.licence.perpetual < licence.libris-certificate(the key must be saved already, or exported asLIBRIS_LICENCE_KEYand passed withdocker compose exec -T -e LIBRIS_LICENCE_KEY api …; exit 0 installed, 1 refused); - or through the API:
POST /api/settings/licence/certificatewith{"certificate": "…", "key": "…"}(administrators;422withinvalid_certificate,key_requiredorkey_mismatch).
The installation is written to the journal (licence=perpetual_installed). From then on Libris never
calls the licence server again — no renewal, no report, no release — and no answer from anybody at
its address can take the certificate away; Settings › Licence reads « Perpetual licence (the
publisher has ended its activity) », with no end date and no renewal button. The words of each month
are still counted locally, for the statistics, without any limit. Accounts, the automation API and
sharing follow the certificate’s features exactly as with the hourly certificate; instances
cannot be checked any more and is only indicative. Activating another key remains possible and
replaces the perpetual certificate — keep the letter with the key.
A Libris published before this change refuses this certificate: update while the
registry is still open (six months after the certificates are sent, the letter gives the date).
The hourly certificate (v: 1) is unchanged: without an end date it is still an expired one.
Currency
The installation counts in one currency, chosen in Settings › Budgets: provider prices are entered in it, every cost computed from them is in it, and every amount the interface shows carries its symbol. Changing it states what the stored numbers are — it does not convert them.
A price list in another currency may be entered as it stands: the price fields carry a currency of their own, and the two prices are converted once, on saving, at the European Central Bank’s daily reference rates. They are fetched by the worker a few times a day (no account, no key, no setting), kept with the date they carry, and used for nothing else. An installation with no outbound access never fetches any: its own currency is then the only one offered, and nothing else changes.
Cost budgets
Spending caps, in the currency the installation counts in; see the user guide and the API reference. A book may have its own cap and an API token its own; these variables are the defaults that an administrator can override in Settings › Budgets.
| Variable | Default | What it does |
|---|---|---|
BUDGET_DEFAULT_BOOK | 0 | Cap of a book that has none of its own, covering everything the book has cost. 0: no default cap. |
BUDGET_SWITCH_THRESHOLD | 0.9 (0.5–1) | Share of a cap from which a running job moves to a cheaper fallback provider, or pauses (budget_exceeded) when there is none (a job that already moved goes on up to the cap). At the cap itself only a provider without a price may go on. |
BUDGET_ON_ESTIMATE | warn | What a launch does when its estimate exceeds what is left of a cap: warn (the job starts, the warning is kept in its report) or refuse. A cap already reached always refuses. |
Work window and daily ceiling
A fair queue knows how to order work; it did not know how to wait. Models are cheaper and less busy at night, electricity often is too, and somebody translating a five-hundred-chapter serial would rather it ran between one and seven than while they are using the machine. A book budget also stops one book, and nothing stopped an installation from spending a month’s worth in an afternoon because four volumes were launched together.
Both live in Settings › Budgets, in the Plage de travail et plafond du jour card; there is no environment variable, because both are decisions somebody changes with the seasons rather than at installation time. Off by default.
- The window is read on the server’s local clock. One that crosses midnight is the ordinary case:
01:00to07:00means the night. A window whose two ends are equal, or that cannot be read, never stops anything — a misconfigured setting must not be a reason to refuse work. - The ceiling is what the day may cost across every book, in the accounting currency.
0: none.
At a daylight-saving change, the next opening is a real instant: the repeated autumn hour may open the window a second time; a missing spring opening advances to the first existing minute inside the window. If the whole window is skipped, work waits until the following day’s opening. The ceiling counts token consumption at each call’s recorded price per million, since local midnight, not the sum of the provider’s price labels. Imported prices are already expressed in the installation’s single accounting currency; no live exchange-rate conversion happens here.
Neither interrupts anything brutally. Outside the window, or over the ceiling, the queue picks up
nothing new, and a job already in flight is deferred at its next model call: it goes back to
waiting with the next opening as its next_attempt, which is the mechanism retries already use, so
it starts again on its own with nothing to click. A job the licence guard pauses needs a person;
this one does not, because nothing is wrong. Its reason is window, and its message says when it
comes back — “paused” without a time reads as broken.
Automation API
Limits of the automation API (/api/v1), used by scripts and other applications with API tokens. See the
API reference.
| Variable | Default | What it does | When to change it |
|---|---|---|---|
API_MAX_PAYLOAD_MB | empty (uses MAX_UPLOAD_MB) | Largest request body (JSON, EPUB or text files) accepted with a Bearer token. | When integrations send bigger books than people upload. |
API_MAX_CHAPTERS | 2000 (1–100000) | Chapters in one request. | For very long books sent as JSON. |
API_RATE_LIMIT_PER_MINUTE | 120 (0–100000) | Calls per token and per minute, counted in each API process. 0 removes the limit. | To throttle or free a busy integration. |
API_RESULT_MAX_WAIT_SECONDS | 60 (0–600) | Longest ?wait= a client may ask for when polling a result. | To allow longer long-polling. |
API_REQUEST_STALL_MINUTES | 360 | A request whose job stays paused, blocked or waiting this long fails, with the reason. | Longer if your provider has long planned outages. |
API_REQUEST_MAX_HOURS | 168 (1–8760) | A request still unfinished after this long fails. No request stays running forever. | Longer for very large books on slow providers. |
DELIVERY_REPAIR_ATTEMPTS | 3 (1–10) | Rounds of automatic repair when a delivered EPUB fails EPUBCheck, before the request fails. | Rarely. |
Fair queue
Jobs are not started oldest first: the worker takes them by priority, then from the account with the fewest jobs running, in turn between accounts (see architecture). These variables are defaults that an administrator can override in Settings › Queue, where quotas can also be set per account.
| Variable | Default | What it does | When to change it |
|---|---|---|---|
QUEUE_MAX_RUNNING_PER_ACCOUNT | 0 (0–1000) | Jobs of one account (the books’ owner) running at the same time. The next ones wait their turn. 0: no limit other than the providers’ capacity. | On a shared installation, so that one account cannot take every provider slot. |
QUEUE_MAX_QUEUED_PER_ACCOUNT | 0 (0–100000) | Jobs and automation requests of one account waiting to start. Beyond it, a new launch or request is refused with HTTP 429 (queue_full). 0: no limit. | To stop one integration from filling the queue. |
QUEUE_MAX_WORDS_PER_ACCOUNT | 0 (0–1000000000) | Words a calendar month (UTC) one account may translate of the licence’s quota — the calendar month, not the licence’s quota cycle — source words, counted when the book is added and charged to its owner. Beyond it (margin LICENCE_QUOTA_OVERRUN_WORDS included) a new book is refused; a book already counted is always translated. 0: no limit of its own. | On a shared installation, to share the month between the members. |
QUEUE_PRIORITY_AGING_MINUTES | 60 (0–10080) | A waiting job rises by one priority level each time it has waited this long, so a low-priority job is never starved. 0: priorities never change by themselves. | Lower it when low-priority work must not wait long behind a stream of high-priority jobs. |
Webhooks
Webhooks notify an integration when an API request finishes. They are off until at least one host is allowed. These variables are defaults that an administrator can override in Settings › Automation API.
| Variable | Default | What it does |
|---|---|---|
API_WEBHOOK_HOSTS | empty (webhooks refused) | Hosts a callback_url may name, comma-separated. *.example.org allows every subdomain of example.org (not example.org itself). |
API_WEBHOOK_PRIVATE_NETWORKS | empty | Private networks (CIDR, e.g. 10.0.0.0/8) that callbacks may reach anyway. By default a callback that resolves to a private, loopback or reserved address is refused. |
API_WEBHOOK_SECRET | empty | Global HMAC signing secret, at least 32 characters. A token’s own webhook secret wins over it. |
API_WEBHOOK_MAX_ATTEMPTS | 6 (1–20) | Delivery attempts per request. |
API_WEBHOOK_TIMEOUT_SECONDS | 10 (1–60) | Timeout of each call. |
Nextcloud / WebDAV connections
Each account may save connections to Nextcloud, ownCloud, a NAS or any WebDAV server (My account ›
Nextcloud / WebDAV connections), to import sources from it and to send finished books to it. The connector
is an outgoing HTTP client aimed at an address people type, so it is off until an administrator lists the
servers it may reach — as with SOURCE_WATCH_HOSTS, an installation upgrading to this version reaches nothing.
These are installation settings (environment only).
| Variable | Default | What it does |
|---|---|---|
WEBDAV_HOSTS | empty (connector absent) | Servers an account may connect to, comma-separated. *.example.org allows every subdomain of example.org (not example.org itself). |
WEBDAV_PRIVATE_NETWORKS | empty | Private networks (CIDR, e.g. 192.168.1.0/24) a listed server may resolve to — a NAS on the LAN. Elsewhere, a name that resolves to a private, loopback or reserved address is refused. Plain HTTP is accepted only inside these networks; everywhere else HTTPS is required and its certificate verified. |
WEBDAV_TIMEOUT_SECONDS | 30 (1–300) | Timeout of each call to a WebDAV server. |
Whatever these allow, the client keeps the rules of the webhooks and watched sources: the name is resolved
once per call and the connection goes to the address checked (no DNS rebinding), the proxy variables of the
server are ignored, a redirection is followed only to the same scheme, host and port (never elsewhere, so the
credentials never leave the server they were typed for), and what is read is bounded — MAX_UPLOAD_MB for a
file, like a file sent from the browser, a few megabytes for a folder listing. Passwords are encrypted with
SECRET_KEY like providers’ keys, never returned by the API and never logged; after a SECRET_KEY change the
interface asks for them again. Sending a book is queued for the worker with the retries of the library
publication; a destination folder that does not exist is created (MKCOL), inside the connection’s address
only. A connection may also receive every finished book of its account automatically (an option of the
connection, off by default): nothing to configure on the installation beyond WEBDAV_HOSTS.
Memory and web search
| Variable | Default | What it does | When to change it |
|---|---|---|---|
OPENVIKING_URL | empty | Address of an external OpenViking memory service. Empty: books use Libris’ internal memory. | Only if you run OpenViking; see OpenViking. Can be set in Settings › Memory · OpenViking instead. |
OPENVIKING_API_KEY | empty | Key for OpenViking. | With OPENVIKING_URL. |
OPENVIKING_ROOT_URI | viking://resources/epub-translator | Root under which Libris publishes its resources in OpenViking. | To share one OpenViking server between several Libris installations. |
OPENVIKING_CLEANUP_ON_DELETE | false | true: deleting a volume or a series also removes its OpenViking documents; the worker does it after the deletion, with retries (see cleanup). | To reclaim space on the OpenViking server. Can be switched in Settings › Memory · OpenViking instead. |
MEMORY_CATALOG_INTERVAL_SECONDS | 60 (10–86400) | How often the worker refreshes the published book catalogue in external memory. | Higher to reduce load on OpenViking. |
SEARXNG_URL | empty (search off) | SearXNG instance used for terminology searches during the final review. Setting it turns search on. | To let the final review look terms up on the web. Search terms are sent to your instance and its upstream engines. |
Codex bridge
Used only by the optional Codex · ChatGPT account provider; see Codex.
| Variable | Default | What it does |
|---|---|---|
CODEX_BRIDGE_TOKEN | generated | Private secret between the API and the bridge container (at least 32 characters, or the bridge refuses to start). python3 scripts/enable_codex.py adds it to an older .env. |
CODEX_BRIDGE_URL | http://codex:8092 | Address of the bridge on the Compose network. |
Quality judge
A model of another provider that reads each finished passage and has its errors corrected; see Quality judge. Settings › Autopilot and a book’s settings override the variable.
| Variable | Default | What it does |
|---|---|---|
QUALITY_JUDGE_PROVIDER | empty | Name or id of the provider that judges (Codex, an OpenAI- or Anthropic-compatible API). Empty: no judge. |
QUALITY_REWORK_SHARE | 0.25 (0–1) | Largest share of a job’s passages corrected after the judge; 0: judged, never corrected. |
Server resources
| Variable | Default | What it does | When to change it |
|---|---|---|---|
EVENT_STREAMS_PER_USER | 4 (1–100) | Live progress connections (one per browser tab open on a book) per account. Beyond it the API answers 429 and asks to close tabs. | If people legitimately keep many books open. |
EVENT_STREAMS_TOTAL | 100 (1–10000) | Live progress connections for the whole API process. | On instances with many simultaneous users. |
PREVIEW_CACHE_MB | 64 (0–4096) | Memory kept for the unpacked books behind recent chapter previews. 0 disables the cache. | Lower on small machines, higher if previews of large books are slow. |
EPUBCHECK_CONCURRENCY | 2 (1–16) | EPUBCheck validations run at once in each process (one Java process each). | Higher on machines with spare cores and memory. |
EPUBCHECK_MAX_HEAP_MB | 1024 (128–16384) | Memory ceiling of each EPUBCheck run. | Higher if validation of very large books fails; lower on small machines. |
Data retention
The worker cleans up diagnostic data once at start-up and then every hour. 0 disables a rule. What each rule
keeps, and how to measure it before applying, is explained in operations.
| Variable | Default | What it removes |
|---|---|---|
RETENTION_REQUEST_BODIES_DAYS | 30 | Prompt, raw response and context trace of finished model requests older than this. The request row, tokens, cost and cached answer stay. |
RETENTION_REQUEST_ROWS_DAYS | 0 (keep) | Whole request rows older than this, once counted in the daily usage totals. Statistics stay right; the response cache and the request inspector lose those rows. 180 is a reasonable value. |
RETENTION_EVENTS_DAYS | 7 | Progress events older than this, always keeping the last 500 of each book. |
RETENTION_OUTBOX_SENT_DAYS | 7 | OpenViking updates already delivered. |
RETENTION_BIBLE_REVISIONS | 20 | Automatic Book Bible revisions beyond the most recent 20 per book. Human revisions are always kept. |
RETENTION_JOB_STATE_DAYS | 30 | Per-passage resume state of jobs finished, failed or cancelled longer ago. Final-review outcomes are kept. |
RETENTION_RESULTS_DAYS | 30 | Result files of automation requests finished longer ago. The request and its report stay; asking for the result again rebuilds it. |
Settings changed in the interface
Administrators can change the following without restarting anything, from Settings in the web interface (or the matching API route, with an administrator session). The new value applies to the next decision the worker makes.
| Settings page | API route | Replaces |
|---|---|---|
| Autopilot | GET, PUT, DELETE /api/settings/autopilot | All AUTOPILOT_* variables, and QUALITY_JUDGE_PROVIDER |
| Automation API (webhooks part) | GET, PUT, DELETE /api/settings/webhooks | All API_WEBHOOK_* variables |
| Budgets | GET, PUT, DELETE /api/settings/budget | All BUDGET_* variables |
| Automatic recovery | GET, PUT, DELETE /api/settings/recovery | PROVIDER_RECOVERY_BASE_SECONDS (5–3600 seconds). PROVIDER_RECOVERY_MAX_SECONDS still applies. |
| Queue | GET, PUT, DELETE /api/settings/queue | All QUEUE_* variables, plus quotas and a priority ceiling per account that have no variable |
GET, PUT, DELETE /api/settings/mail, POST /api/settings/mail/test | SMTP_* defaults; an explicit saved disable also disables sending. The inspector and retry routes are described in SMTP administration. | |
| Memory · OpenViking | GET, PUT /api/settings/memory, POST /api/settings/memory/test | OPENVIKING_URL, OPENVIKING_API_KEY, OPENVIKING_ROOT_URI, plus search options, budgets, minimum score, timeout and authentication mode that have no variable |
| Memory · OpenViking (card OpenViking cleanup) | GET, PUT, DELETE /api/settings/memory/cleanup | OPENVIKING_CLEANUP_ON_DELETE |
| SearXNG | GET, PUT /api/settings/searxng, POST /api/settings/searxng/test | SEARXNG_URL, with a separate on/off switch |
Other Settings pages (LLM providers, Prompts, Users, API tokens) hold data that exists only in the database; they have no environment equivalent.
Which value wins
From strongest to weakest:
- The launch or request itself, e.g.
"autopilot": false,"final_review": false,analysis_modeorthreadsin a launch or an API request. - The volume’s own settings: its autopilot switch, fallback providers, passage size, review mode, analysis mode, passages worked on at once and cost budget.
- A value saved in Settings.
- The environment variable in
.env. - The built-in default shown on this page.
Details per page:
- Autopilot, Budgets and webhooks show the effective values next to the environment values. Go back to the
environment values (
DELETE) forgets everything saved on that page. Fallback providers are saved as provider ids; an unknown name is refused, and a provider deleted later is skipped. - The global webhook secret entered in the interface is stored encrypted with
SECRET_KEYand never shown again. It wins overAPI_WEBHOOK_SECRET; Forget the secret saved here falls back to the variable. IfSECRET_KEYchanged and the saved secret cannot be read, the variable is used. - Automatic recovery shows the delay in force, the value of
PROVIDER_RECOVERY_BASE_SECONDSand whether the delay was saved here (badge Delay saved here or Environment delay). Go back to the environment delay (DELETE) forgets the saved delay; the variable applies again to the next retries. - Queue shows the values in force next to the environment values. Per-account rows override the
installation’s quotas for one account (empty: the installation’s value;
0: no limit for that account) and may allow it to ask for High priority, which is otherwise reserved to administrators. An API token can have lower limits of its own (see API tokens). Go back to the environment values (DELETE) forgets the page, per-account rows included. - Memory · OpenViking overrides the environment field by field. A key saved there is encrypted with
SECRET_KEY; if it can no longer be read, OpenViking search is turned off and translation continues with the internal memory. - OpenViking cleanup shows the switch in force, the value of
OPENVIKING_CLEANUP_ON_DELETEand whether the switch was saved here. Go back to the environment value (DELETE) forgets it. The switch applies to the next deletions; cleanups already queued still run. Blocked counts cover the entire queue; an administrator can retire a pending cleanup after a root change without deleting its documents. The independent daily orphan scan is off by default and never removes anything. See OpenViking cleanup. - SearXNG replaces the environment completely once saved: the saved URL and switch are used, even if
SEARXNG_URLis set.