Reference: Configuration
Start
OverviewConceptsGuides
Run and configureREST API walkthroughCommand lineQuery groupsNotebooksRules and SRLPrefixesTestingMCP clientsPublish as toolsQuery bench in chatStatic exportETLDeployingReference
ConfigurationFeature flagsEntity modelSRL languageScriptsExplanation
ArchitectureVersioningEnvironmentsStorage and cachingSecurity modelRDF PatchInline inputsExamples
Sudoku solverConfiguration reference
Every environment variable the server reads, with the default that applies when it is unset and what setting it changes. Defaults here are taken from the code that reads each variable.
Two processes read this environment and they are not the same
process. The API (packages/api) reads PORT and
FASTIFY_ADDRESS; the MCP runtime
(packages/mcp-server), which mounts the API in-process,
reads HTTP_PORT, HTTP_HOST and the
MCP_* variables. The container starts one of them according
to APP_MODE. The web app is a separate static build with
its own NUXT_PUBLIC_* variables, listed at the end.
For the reasoning behind these choices rather than their values, see running and configuring and deploying.
Server and transport
| Name | Default | Effect |
|---|---|---|
APP_MODE |
dual-http (set in the image) |
Read only by the container's start command. api runs
packages/api/dist/index.js; mcp-http runs the
MCP runtime with MCP_TRANSPORT=streamable-http;
dual-http, and any other value, runs it with
MCP_TRANSPORT=dual-http. Outside the container nothing
reads it. |
MCP_TRANSPORT |
unset: streamable-http when
NODE_ENV=production, otherwise stdio |
Transport for packages/mcp-server. Accepts
stdio, http or streamable-http,
and dual-http or http+api.
dual-http serves the REST API and MCP at /mcp
on one port. |
HTTP_PORT |
3000 |
Listening port in dual-http mode. |
HTTP_HOST |
0.0.0.0 |
Listening address in dual-http mode. Falls back to
MCP_HTTP_HOST when unset. |
MCP_HTTP_PORT |
3333 |
Listening port in streamable-http mode (MCP only, at
/mcp). |
MCP_HTTP_HOST |
0.0.0.0 |
Listening address in streamable-http mode. |
MCP_CATALOGUE |
both |
Which surfaces the server offers: both,
catalogue or published. catalogue
serves no published tools (/mcp/{slug} and
/tools/… are 404). published serves only the
published tools: /mcp lists them alone, every authoring
route is 404, POST /execute runs only their pinned
versions, and an invalid published tool stops the server at startup
instead of being skipped. Anything else throws. See publishing tools. |
SQLIB_CORS_ORIGINS |
unset: http://localhost:3001,http://127.0.0.1:3001 (the
SPA dev server) when NODE_ENV=development, otherwise
none |
Browser origins allowed to read API and /mcp responses,
comma-separated. A listed origin is echoed in
access-control-allow-origin with
access-control-allow-credentials: true; *
allows every origin and never sends credentials; an origin not on the
list gets no CORS headers, and its preflight gets 404. Set to the empty
string to allow none even in development. A production deployment whose
SPA is served from another origin than the API must list that origin.
The API (packages/api/src/config/cors.ts) and the MCP
transport read it the same way, since in dual-http and
streamable-http modes they share one instance;
/mcp answers its own OPTIONS /mcp preflight,
with the MCP headers, and the API's plugin leaves that path alone. |
PORT |
3000 |
Listening port when packages/api is started directly
(APP_MODE=api). A value that does not parse as a number
falls back to 3000. |
FASTIFY_ADDRESS |
0.0.0.0 |
Listening address when packages/api is started
directly. |
APP_BASE_PATH |
empty | Mounts every API route under this prefix. A leading slash is added
and trailing slashes are stripped; / means no prefix. |
APP_PUBLIC_BASE_PATH |
value of APP_BASE_PATH |
The prefix the server advertises in generated URLs, for a deployment behind a proxy that rewrites the path. |
NODE_ENV |
unset | Standard Node convention. It selects the MCP transport default
above, switches OTLP export on in development (see
telemetry), and suppresses OpenTelemetry logging when it is
test. |
MCP_DEBUG_PORT |
3334 |
Port for packages/mcp-server's echo server
(pnpm --filter @sparql-query-lib/mcp-server dev:debug-http),
a development aid, not part of the served application. |
MCP_DEBUG_HOST |
127.0.0.1 |
Address for the same. |
Library storage
The library — every entity you author — lives in a store chosen by
INTERNAL_BACKEND_TYPE. This is separate from the backends
your queries run against, which are entities you create through the
API.
| Name | Default | Effect |
|---|---|---|
INTERNAL_BACKEND_TYPE |
sqlite |
sqlite, http, oxigraph-memory
or oxigraph-persistent. Unset or empty means
sqlite. Any other value throws at startup. |
LIBRARY_STORAGE_SPARQL_ENDPOINT |
http://localhost:3030/sqlib/ |
With INTERNAL_BACKEND_TYPE=http, the SPARQL endpoint
holding the library. The default is a local Fuseki on its conventional
port, so an unconfigured server fails against localhost rather than
reaching an external host. |
LIBRARY_STORAGE_SPARQL_QUERY_ENDPOINT |
value of LIBRARY_STORAGE_SPARQL_ENDPOINT |
Query URL, when the store separates query from update. |
LIBRARY_STORAGE_SPARQL_UPDATE_ENDPOINT |
value of LIBRARY_STORAGE_SPARQL_ENDPOINT |
Update URL, when the store separates query from update. |
LIBRARY_STORAGE_SPARQL_USERNAME |
unset | Basic-auth username for the library endpoint. |
LIBRARY_STORAGE_SPARQL_PASSWORD |
unset | Basic-auth password for the library endpoint. |
LIBRARY_STORAGE_DIR |
./storage/library-store, resolved against the process
working directory |
With INTERNAL_BACKEND_TYPE=sqlite, the directory
holding library.sqlite (and its -wal and
-shm files); :memory: keeps the database in
the process, for tests. With oxigraph-persistent, the
directory holding the .nq serialisation of the
library. |
INTERNAL_OXIGRAPH_STORE_ID |
library-store |
Name of the persistent store within that directory. |
INTERNAL_OXIGRAPH_CHECKPOINT_INTERVAL_MS |
60000 |
How often the in-memory store is written back to
.nq. |
INTERNAL_OXIGRAPH_LOAD_METHOD |
none |
How the store is populated at boot: file,
remote-sparql, remote-file or
none. An unrecognised value falls back to
none. |
INTERNAL_OXIGRAPH_BOOTSTRAP_SOURCE |
unset | A JSON object describing the source for that load method. Invalid JSON is logged as a warning and ignored. |
OXIGRAPH_STORAGE_DIR |
./storage/oxigraph |
Directory for in-process Oxigraph backends you create as entities —
the stores behind oxigraphEphemeral and
oxigraphMemory backends. It is not where the library lives
unless INTERNAL_BACKEND_TYPE=oxigraph-persistent, in which
case LIBRARY_STORAGE_DIR takes over for the library
store. |
ENABLE_OXIGRAPH |
false |
Set to true to initialise the Oxigraph store manager
even when the library is not in Oxigraph, so that Oxigraph-backed
backend entities work. It is initialised unconditionally when
INTERNAL_BACKEND_TYPE=oxigraph-persistent. |
DEBUG_OXIGRAPH |
false |
Set to true to log every SPARQL operation the
in-process Oxigraph executor runs. |
What
oxigraph-persistent actually does
The Oxigraph JavaScript/WebAssembly bindings support in-memory stores
only. There is no RocksDB-backed store as there is in the Rust and
Python bindings. oxigraph-persistent is an in-memory store
that is serialised to .nq files in
LIBRARY_STORAGE_DIR on shutdown and every
INTERNAL_OXIGRAPH_CHECKPOINT_INTERVAL_MS milliseconds, and
restored from those files at startup. A process killed between
checkpoints loses the writes made since the last one.
oxigraph-memory keeps nothing.
For a store that is durable in the usual sense, use the default
sqlite, or run a SPARQL server such as Oxigraph's own or
Fuseki alongside and use INTERNAL_BACKEND_TYPE=http.
INTERNAL_OXIGRAPH_DB_PATH is no longer read. It named a
RocksDB directory that these bindings cannot open; serialisation goes to
LIBRARY_STORAGE_DIR.
See storage and caching for how the store and the in-memory cache relate.
Execution backend credentials
A Backend entity does not store credentials. It stores
an authEnvKey, and the server looks up the credentials in
the environment under that key. This keeps secrets out of the library
store, which is exported, imported and copied.
authEnvKey must match ^[A-Z0-9_]+$. For a
backend whose authEnvKey is <KEY>, the
server reads, in this order:
| Name | Effect |
|---|---|
SQLIB_BACKEND_<KEY>_USERNAME and
SQLIB_BACKEND_<KEY>_PASSWORD |
Both present: HTTP Basic. The pair is base64-encoded into
Authorization: Basic …. |
SQLIB_BACKEND_<KEY>_AUTH_HEADER |
Used when the username/password pair is incomplete. Sent verbatim as
the Authorization header, so it must include its scheme
(Bearer …). |
| neither | No Authorization header is sent. |
<KEY> is the entity's authEnvKey
upper-cased. A backend with authEnvKey: "qms_fuseki_dev"
reads SQLIB_BACKEND_QMS_FUSEKI_DEV_USERNAME and
SQLIB_BACKEND_QMS_FUSEKI_DEV_PASSWORD. A backend with no
authEnvKey sends no credentials.
| Name | Default | Effect |
|---|---|---|
HTTP_SPARQL_USER_AGENT |
sparql-query-lib/adhoc |
User-Agent sent to HTTP SPARQL endpoints. |
SQLIB_BACKEND_PROBE_TIMEOUT_MS |
10000 |
Timeout for the reachability probe run when a backend is created or checked. Values that are not a positive finite number fall back to the default. |
SQLIB_BACKEND_SLOW_MS |
250 |
Above this round-trip time a probed backend is reported as slow. |
SQLIB_PREFIX_DETECT |
on | Set to off to skip detecting a backend's namespace
prefixes during the probe. Any other value leaves detection on. |
SQLIB_PREFIX_TIMEOUT_MS |
10000 |
Timeout for the prefix-detection query. See prefixes. |
Caching
| Name | Default | Effect |
|---|---|---|
CACHE_WRITE_THROUGH |
true |
Every entity write goes to the store as well as the in-memory cache.
Set to exactly false to keep writes in memory only; any
other value leaves it on. With it off, background refreshes from the
store are skipped too. |
CACHE_PRELOAD |
true |
Load every entity into the cache at startup. Set to
false (case-insensitive) to skip the preload; with it off,
background per-type refreshes are also skipped. |
ENABLE_TIMING_LOGS |
true, and not changeable |
Intended to switch per-request timing logs. The expression that
reads it is
process.env.ENABLE_TIMING_LOGS === 'true' || true, so
config.enableTimingLogs is always true and
setting the variable has no effect. |
Entity time-to-live in the cache is not configurable by environment:
it is set per entity type in
packages/api/src/lib/EntityRegistry.ts, where immutable
versions are held indefinitely. See storage and
caching.
ETL
ETL is off by default; see feature flags and the ETL guide.
| Name | Default | Effect |
|---|---|---|
ETL_OUTPUT_DIR |
./storage/etl-output, resolved against the process
working directory |
Where an ETL execution writes its RDF output files. The container creates this path with the right ownership, so it is writable on a fresh volume. Not written on a read-only deployment. |
ETL_RESPONSE_OUTPUT_MAX_BYTES |
1048576 |
The most RDF a run returns in its response on a read-only deployment, where it writes no file. A run whose output passes it fails, naming the cap, rather than returning part of its output. |
SQLIB_ETL_NODE_MAX_ROWS |
1000000 |
The most rows an ETL node inside a query group may read from its source. A source with more fails the node, naming the cap, rather than handing the rest of the group a truncated result. Running a job directly is not affected. |
Kept results (pass-by-reference)
A run that sets keepResult has its response kept in
server memory, and the response carries a Sqlib-Result-Ref
handle. A later request names the handle (argumentRefs or
dataGraphs[].resultRef on /execute,
dataGraphResultRef on a rule set run) instead of sending
the content again. Only responses the server produced are kept, and a
handle is readable only by the subject that made the run. A missing,
expired or foreign handle returns 410.
| Name | Default | Effect |
|---|---|---|
RESULT_CACHE_MAX_BYTES |
268435456 (256 MiB) |
All kept results together. The oldest are dropped first when a new
one would pass it. 0 turns the feature off: nothing is
kept, and every handle returns 410. |
RESULT_CACHE_MAX_BYTES_PER_USER |
10485760 (10 MiB) |
One subject's kept results together. A larger single result is not kept; otherwise the subject's oldest are dropped first. |
RESULT_CACHE_TTL_MS |
900000 (15 minutes) |
Time after its last use at which a kept result is dropped. |
A restart empties the cache.
Entity size limits
| Name | Default | Effect |
|---|---|---|
DATA_GRAPH_MAX_VERSION_BYTES |
1048576 (1 MiB) |
Largest single data graph version. |
DATA_GRAPH_MAX_LIBRARY_BYTES |
16777216 (16 MiB) |
Largest total of data graph content in one library. |
TUPLE_SET_MAX_VERSION_BYTES |
1048576 (1 MiB) |
Largest single tuple set version. |
TUPLE_SET_MAX_LIBRARY_BYTES |
16777216 (16 MiB) |
Largest total of tuple set content in one library. |
A value that does not parse as an integer falls back to the default.
Rule execution and patches
| Name | Default | Effect |
|---|---|---|
RULE_EXECUTION_TIMEOUT_MS |
30000 |
Per-rule timeout during a rule set run. A rule is judged against it
after it finishes; to stop a run, use
SQLIB_EXECUTION_TIMEOUT_MS. |
SQLIB_EXECUTION_TIMEOUT_MS |
300000 (5 minutes) |
The most one query group run or rule set run may take. Checked
before every group node and every rule and DATA block, and aborts HTTP
requests to backends in flight; a run past it fails at the node it was
on, with status 504 from /execute. It cannot interrupt a
single query against an in-process Oxigraph store, which runs
synchronously, so such a run stops at the next boundary after the
deadline. 0 turns it off. A client that disconnects from
/execute or a rule set's execute route cancels its run the
same way. |
SQLIB_MAX_RULE_ITERATIONS |
1000 |
The most fixpoint iterations a rule set run may take per stratum. A
request asking for more maxIterations is refused by the
route schema (read at startup); the executor clamps to the value in
force whatever it is asked. |
RULE_EXECUTION_SAMPLE_LIMIT |
10 |
How many sample bindings a rule run reports per rule. |
PATCH_PREVIEW_TTL_MS |
3600000 (1 hour) |
How long a derived, unapplied RDF patch preview is retained. See RDF patch. |
PATCH_SWEEP_INTERVAL_MS |
600000 (10 minutes) |
How often expired previews are swept. |
Telemetry
| Name | Default | Effect |
|---|---|---|
OTEL_ENABLED |
true |
Set to exactly false to skip OpenTelemetry SDK startup
and OpenTelemetry log export. Any other value leaves it on. With it off
the SDK is not imported at all, which is worth about 220ms of
startup. |
OTEL_EXPORTER_OTLP_ENDPOINT |
http://localhost:4318 |
OTLP HTTP collector base URL. Traces go to /v1/traces
and metrics to /v1/metrics. |
OTLP export only happens when NODE_ENV=development. In
any other environment the SDK starts with console exporters, which means
an endpoint set in production is not used. Metrics are exported every 30
seconds.
Startup time
Relevant to a deployment that scales to zero, where a cold start sits in front of a user request rather than happening once a week.
| Name | Default | Effect |
|---|---|---|
SQLIB_EAGER_SERIALIZERS |
unset (lazy) | Set to exactly true to compile every route's response
serialiser while the server starts, instead of on that route's first
response. |
NODE_COMPILE_CACHE |
unset in a local run, /app/.v8-compile-cache in the
container |
Node's own variable: a directory where it caches compiled module bytecode and reuses it next boot. The image ships a populated cache, so the first container to start is already warm. |
Response-schema compilation is the largest single term in startup and
it scales with the number of routes, not with the amount of data: on a
135-route instance it was ~890ms of a ~1.4s ready(),
whether or not anything ever called those routes. Compiling each route's
serialiser on its first response instead moves that off the boot path
for ~2ms on that first response, which is why it is the default.
SQLIB_EAGER_SERIALIZERS=true is the way back for a
long-lived instance that would rather pay the whole bill before it
accepts traffic.
Two other things help and are ordinary configuration rather than
switches: turning off feature flags you
do not serve removes their routes, and each route is worth roughly 10ms
of startup; and OTEL_ENABLED=false avoids loading the
OpenTelemetry SDK.
What does not help much is anything about Oxigraph itself:
the WASM module instantiates in about 25ms. The data-dependent part of
startup is the snapshot, which loads at roughly 180,000 quads per
second, plus the cache preload — so a library of a few thousand entities
is lost in the noise and one of a few hundred thousand quads is not. See
what
oxigraph-persistent actually does.
Read-only deployments
SQLIB_READ_ONLY=true turns sqlib into a compute service:
it still parses, validates, formats and executes, but it refuses every
write to its own state. The catalogue changes by redeployment, and
anything a visitor authors is kept in their browser instead. It is what
a public demo site runs.
| Name | Default | Effect |
|---|---|---|
SQLIB_READ_ONLY |
false |
Exactly true turns it on; any other value, including
unset, leaves it off. |
It is not an auth mode and does not need one. An auth mode answers
"who is this, and what may they reach", which needs principals, grants
and an issuer; this answers "may this deployment's own state change at
all", which gives every caller the same answer. The two compose — a
public site runs SQLIB_AUTH_MODE=disabled beside this, and
the full-access context that mode mints still cannot write, because the
gate consults no context.
The gate refuses every mutating method and then names its exceptions,
so a route added later is refused until someone decides otherwise. A
route that writes on a safe method declares it with
config: { readOnlyMutating: true } in its route options,
and is refused like an unlisted POST. The exceptions are
the routes that compute an answer and store nothing:
/detect-inputs, /detect-outputs,
/validate, /validate-rule-data,
/format, /substitute, /execute,
/sparql, the SRL compile, analyse and preview routes, the
rule and rule-set execution routes, the tuple-set preview, the two
test-run routes, POST /etl-jobs/:id/execute,
POST /benchmark-experiments/:id/versions/:version/run, and
/mcp. The list lives in
packages/api/src/config/readOnly.ts, and a test fails on an
entry that names no registered route, on a mutating route that is
neither listed nor refused, and on one of these exceptions losing its
way back.
Four of those exceptions are worth their own sentence:
- Running a test is compute; its history is the
write.
POST /tests/runandPOST /tests/:id/runanswer with a verdict, andrecordTestRunsfiles nothing on a read-only deployment, so no run history accumulates — the same line?record=patchdraws for SPARQL. - Running a stored ETL job is compute; its record and its file
are the write. On a read-only deployment
POST /etl-jobs/:id/executeopens noEtlExecutionand writes no output file, and returns the RDF in the response asoutput, up toETL_RESPONSE_OUTPUT_MAX_BYTES. What runs is the job version's stored SQL; the request carries none. The run reads at mostSQLIB_READ_ONLY_ETL_MAX_ROWSsource rows, and when the request asked for more (or for no limit) the response says so inrowLimitApplied. - Running a frozen benchmark version is compute; its runs and
observations are the write. On a read-only deployment the run
route accepts only a version that is already frozen (it does not freeze
one), returns the run and its observations in the response, and stores
none of them. A plan over the request cap is refused with 422, naming
the cap; a second run while one is in flight gets 429 with
Retry-After. The freeze route stays refused.
The caps on those runs, read only on a read-only deployment and
reported by /health as readOnlyRunLimits:
| Name | Default | Effect |
|---|---|---|
SQLIB_READ_ONLY_BENCH_MAX_REQUESTS |
200 |
The most requests one benchmark run may send: measured tasks plus warmups. A version over it is refused, not clamped, because a clamped benchmark reports numbers for a plan nobody saved. |
SQLIB_READ_ONLY_BENCH_MAX_CONCURRENCY |
2 |
The most a version's maxConcurrency may ask for. |
SQLIB_READ_ONLY_BENCH_DEADLINE_MS |
60000 |
The wall-clock limit for a whole benchmark run. When it passes, no
task starts, the attempts in flight are aborted, and the run ends
Aborted with what it finished. |
SQLIB_READ_ONLY_BENCH_CONCURRENT_RUNS |
1 |
Benchmark runs allowed at once on the deployment.
disabled auth gives no client identity, so the count is
deployment-wide. |
SQLIB_READ_ONLY_ETL_MAX_ROWS |
100000 |
The most source rows one ETL job run may read. A clamp rather than a refusal: a visitor running part of a source is asking for a sample. |
/mcpis all POST, so refusing it would take every tool, reads included, off a read-only deployment. Admitting it opens no write path: a tool call reaches its route throughapp.injectand meets the same hook there, so a mutation tool is refused at the inner route.
Three things it deliberately does not do:
- It does not refuse SPARQL against a registered
backend.
POST /sparqlwith abackendIdpasses through, UPDATEs included. A bareendpointis refused with405, for the reason/backends/probesis: a public server that fetches a URL the caller chose is a request forgery primitive. Which SPARQL a visitor may run is a grant, not this flag:SQLIB_AUTH_MODE=anonymouswithquery:savedon a backend admits the seeded queries and nothing typed. Whether a store accepts a write is the store's answer: sqlib's own read-only backends refuse throughReadOnlySparqlExecutor, and an endpoint somebody else owns refuses, or does not, on its own terms. What it does refuse is?record=patch, onGETandPOSTalike, because recording a patch writes sqlib's state. (GET /sparqlrefuses an UPDATE with 405 on every deployment, read-only or not: a GET must not change anything.) - It does not enable anything.
FEATURE_ETL,FEATURE_PLAYGROUND_ETLandFEATURE_ASSISTANTare still off by default, and the ETL routes that take SQL in the request —POST /etl-jobs/previewandPOST /playground/etl/execute— are absent from the exceptions above, as is every route that would store a job version. Turning a flag on is not enough to let a visitor's own SQL run on a read-only deployment. - It does not authenticate. Every visitor is the same anonymous caller.
/health reports it as readOnly, beside the
auth mode, so the SPA can offer browser-local authoring rather than a
button the server will refuse.
The SPA draws no control whose only outcome is a refusal. Save and
the entity ⋮ go from every work area, the benchmark and
test chips go from the run bar,
Save to Tuples, Save to Data and
Save as test go from a rule set's Inputs tab, and the tag
controls go from the Details panel — absent rather than disabled, the
rule the rail already follows for a switched-off section. Everything
acting on the browser's own copy stays: scratch queries, rule sets and
argument sets, the values in them, Discard, and the inline data a rule
set runs against. A parameterised query is authored and run the same way
it is on a writable deployment, with the values sent inline to
POST /sparql.
A visitor's own SPARQL endpoint is registered the same way. The
backend picker in the run sentence takes a URL pasted straight into it —
+ SPARQL endpoint… at the foot of the menu — and keeps it
as a browser backend: stored in that browser, named
after its own URL, and never posted to the server. Renaming one, or
changing its query method, is done under Backends like
any other, and writes to the browser rather than to the server. Queries
against it run from the browser to the endpoint, so the address, any
headers and the results do not pass through sqlib. Browser backends are
not a read-only feature — they work on any deployment — but they are
what makes a read-only one usable against data it was not seeded
with.
Before /health answers, and if it cannot be reached, the
SPA assumes it may write. That direction is deliberate: a client that
wrongly believes it may write shows a button and surfaces the server's
refusal, while one that wrongly believes it may not hides a working
feature and looks broken.
Authentication
Auth is off unless SQLIB_AUTH_MODE says otherwise. An
invalid value for any of these throws at startup rather than falling
back. See the security
model for what the layer does and does not cover.
| Name | Default | Effect |
|---|---|---|
SQLIB_AUTH_MODE |
disabled |
disabled, dry-run (or
dryrun), required or anonymous.
An empty value is disabled. Anything else throws.
dry-run and required both require an issuer,
and an audience for every issuer unless
SQLIB_AUTH_AUDIENCE_UNCHECKED=true. anonymous
reads no token: every caller is
urn:sqlib:principal:anonymous, holds the grants on that
principal, and is refused everything else. For a public deployment
beside SQLIB_READ_ONLY=true; seed the grants with
SQLIB_AUTH_SEED_GRANTS. |
SQLIB_AUTH_ISSUER |
unset | OIDC issuer URL for a single-issuer deployment. |
SQLIB_AUTH_AUDIENCE |
unset | Expected aud for that issuer. |
SQLIB_AUTH_AUDIENCE_UNCHECKED |
false |
Set to exactly true (case-insensitive) to start
dry-run or required with an issuer that has no
audience. Without an audience the aud claim is not checked,
so any token that issuer minted for any application is accepted; without
this flag that configuration throws at startup. Applies to
SQLIB_AUTH_ISSUERS_JSON entries too. |
SQLIB_AUTH_JWKS_URI |
from discovery | JWKS URL. Unset, the jwks_uri of the issuer's OIDC
discovery document
(<issuer>/.well-known/openid-configuration), fetched
once on the first token; an issuer with no discovery document falls back
to <issuer>/.well-known/jwks.json. Set it when sqlib
reaches the broker at another address than the issuer URL, as the
compose auth profile does. |
SQLIB_AUTH_CLAIM_GROUPS |
groups |
Dot-path to the claim carrying group or role membership. |
SQLIB_AUTH_CLAIM_CLIENT_ID |
azp |
Dot-path to the claim used to recognise a machine-to-machine caller. |
SQLIB_AUTH_ISSUERS_JSON |
unset | A JSON array of issuer objects (issuer,
audience — required in an enforcing mode unless
SQLIB_AUTH_AUDIENCE_UNCHECKED=true — and optionally
jwksUri, claimGroups,
claimClientId), for more than one issuer. When set it
replaces the single-issuer variables above. Invalid JSON, a non-array,
an empty array or an entry without a string issuer throws.
Per-entry defaults are groups and azp. |
SQLIB_AUTH_CLOCK_SKEW_S |
60 |
Allowed clock skew in seconds when validating a token. A negative or non-numeric value throws. |
SQLIB_AUTH_ADMIN_PRINCIPALS |
empty | Comma-separated raw claim values treated as administrators at boot,
in the form iss|sub or iss|group:value. |
SQLIB_AUTH_SEED_GRANTS |
unset | Path to a file of grants loaded at startup. |
SQLIB_AUTH_ALLOW_LIBRARY_CREATE |
all |
all or admin. Anything else throws. |
SQLIB_AUTH_PROTECT_DOCS |
false |
Set to exactly true (case-insensitive) to require a
token for the API documentation routes (/docs and
everything under it). /, /health and the
protected-resource metadata stay public regardless. |
SQLIB_AUTH_RESOURCE |
the first issuer's audience | The resource identifier: the resource field of the
protected-resource metadata, and the value an MCP client asks a token
for. Use the canonical URL of the deployment's /mcp
endpoint (for example https://sqlib.example.org/mcp), and
configure the broker to put that value in aud. When it is
an absolute URL, the 401 challenge names the metadata at
<its origin>/.well-known/oauth-protected-resource<its path>;
otherwise at the request's origin. A value that matches no issuer's
audience logs a warning at startup. |
SQLIB_AUTH_STORE_TYPE |
sqlite, or sparql when
SQLIB_AUTH_SPARQL_ENDPOINT is set |
Where grants and principals are kept. Never the library store:
sqlite is one SQLite file, file one N-Quads
file, sparql the urn:sqlib:auth graph of a
SPARQL dataset of its own. Anything else throws. Not read when
SQLIB_AUTH_MODE is disabled; read in every
other mode, anonymous included. |
SQLIB_AUTH_STORE_DIR |
./storage/auth |
The directory of the sqlite and file
stores. sqlite keeps auth.sqlite (with its
-wal and -shm files): one row per quad,
written per change in one transaction, and an auth_events
table that records every quad each change added or removed.
file keeps auth.nq, written whole and renamed
into place on each change. |
SQLIB_AUTH_SPARQL_ENDPOINT |
unset | The SPARQL dataset of the sparql store. Required for
that type. Use a dataset of its own, not the library store's. |
SQLIB_AUTH_SPARQL_UPDATE_ENDPOINT |
the endpoint | Its update endpoint, when it differs. |
SQLIB_AUTH_SPARQL_USERNAME /
SQLIB_AUTH_SPARQL_PASSWORD |
unset | Basic credentials for that dataset. |
SQLIB_AUTH_POLICY_CACHE_SECONDS |
60 |
How long a backend graph policy's result is kept per backend and
caller. 0 evaluates the policy on every read. A change to
the policy, or any update to an auth backend, clears the cache. A
negative or non-numeric value throws. |
SQLIB_AUTH_SCOPES |
empty | Space- or comma-separated scopes. Published as
scopes_supported in the metadata and as scope
in the 401 challenge. Empty leaves both out. |
The API serves OAuth 2.0 Protected Resource Metadata (RFC 9728) at
/.well-known/oauth-protected-resource, at every
path-suffixed form of it
(/.well-known/oauth-protected-resource/mcp), and, under
APP_BASE_PATH, at
<base path>/.well-known/oauth-protected-resource too.
The document lists the configured issuers as
authorization_servers. It is public in every mode, and
answers 404 when no issuer is configured. Every 401 carries
WWW-Authenticate: Bearer resource_metadata="…", which is
how an MCP client finds the authorization server with no client
configuration.
Environments
| Name | Default | Effect |
|---|---|---|
SQLIB_DEFAULT_ENVIRONMENT |
unset | The environment a run uses when the caller names none, and the one
published tools serve. Unset means latest, the current
version. A library that defines no environments ignores it. See environments. |
Feature flags
Seventeen FEATURE_* variables switch sections of the
product on and off, and the API unregisters the routes behind some of
them. They have their own page: feature
flags, which lists each key, its variable, its default and what
disappears when it is off.
Development and seeding
These are read by development recipes and seeders, not by a normal deployment.
| Name | Default | Effect |
|---|---|---|
SEED_W3C_RULES_SUITE |
unset | Set to exactly true to seed the W3C SPARQL RL suite as
runnable tests at startup. Requires the rulesSuite and
tests flags. Seeding is idempotent. |
W3C_RULES_SUITE_DIR |
the suite directory inside packages/api |
Where that suite is read from. A relative path resolves against the API package root. |
SEED_PATCH_DEMO |
unset | Set to exactly true to seed the RDF patch demo library
at startup. Requires the queries flag. |
SYSTEM_STORE_ASSET_DIR |
system-store inside packages/api |
Where the preloaded system library's assets are read from. |
VITEST |
set by vitest | Presence suppresses OpenTelemetry log export during tests. |
The web application
packages/web is a Nuxt SPA built ahead of time
(ssr: false), so these are read at build
time and baked into the bundle.
| Name | Default | Effect |
|---|---|---|
NUXT_PUBLIC_API_BASE_URL |
http://localhost:3000 |
The API the app calls. |
NUXT_PUBLIC_AUTH_ISSUER |
empty | OIDC issuer for the browser's login flow. Empty, the app takes the
first authorization_servers entry of the API's
protected-resource metadata; set, it overrides that. |
NUXT_PUBLIC_AUTH_CLIENT_ID |
empty | OIDC client id. |
NUXT_PUBLIC_AUTH_AUDIENCE |
empty | Requested audience. |
NUXT_PUBLIC_AUTH_SCOPE |
openid profile email |
Requested scopes. Add offline_access for a broker that
issues refresh tokens only on request, and groups for
Rauthy's group claim. |
NUXT_PUBLIC_AUTH_TOKEN_STORAGE |
memory |
Where the signed-in user and its tokens are kept:
memory (no web storage; a reload signs in again through the
broker, without a form while the broker's session lives),
session (sessionStorage: survives a reload in
that tab) or local (localStorage: every tab,
the behaviour before this setting). Anything else is
memory. authTokenStorage in
/config.json sets the same thing. |
NUXT_PUBLIC_AUTH_REQUIRE_LOGIN |
false |
Exactly true makes the app sign in even when the API
reports auth disabled, for an API behind a gateway that
does the authentication. authRequireLogin in
/config.json sets the same thing at load time. |
NUXT_APP_BASE_URL |
/ |
The path the site is served from. Written into every page's asset
links, so the bundle works only at that path.
Dockerfile.web takes it as a build argument. See deploying. |
NUXT_PUBLIC_FEATURE_* |
the API-side default for that flag | Per-flag override for the browser build; see feature flags. |
NUXT_PUBLIC_APP_VERSION |
the version in .release-please-manifest.json |
What the About block on the splash and the line in Settings report. |
NUXT_PUBLIC_APP_COMMIT |
git rev-parse --short HEAD, or empty |
The commit reported beside the version. Shortened to seven characters. |
NUXT_PUBLIC_BUILD_TIME |
the moment of the build | ISO timestamp shown in the version's tooltip. |
The three build-stamp variables exist for builds that cannot work the
values out for themselves: an image built from a source copy with no
.git directory finds no commit, and one built outside the
repo finds no manifest. Pass what the build system knows.
A deployed static build can be re-pointed without rebuilding by
serving a /config.json beside it. The client plugin
packages/web/src/plugins/runtime-config.client.ts fetches
it at startup and overrides apiBaseUrl, the four
auth* values and any subset of featureFlags. A
missing file, a non-JSON response or a parse failure logs a warning and
leaves the build-time values in place. See deploying.