Configuration
VoLCA is configured through a TOML file, typically named volca.toml. Pass it to any command with --config volca.toml.
Top-level scalar fields (such as geographies) must come before any [section] or [[array-of-tables]] header. After a header, every key is parsed as a member of that section – so a top-level key written lower down becomes server.geographies instead, and no longer means anything. Startup warns about the keys nothing reads, naming each one, which is how you find that kind of mistake. It does not reach inside a table whose keys you choose yourself, such as locationAliases or a scoring set’s variables, so a stray key there is still lost quietly.
Where the file lives, and when it is read
Section titled “Where the file lives, and when it is read”| How you run VoLCA | Where its configuration comes from |
|---|---|
volca --config FILE ... | The file you name. Relative paths inside it resolve against the file’s own directory, not your working directory |
volca server with no --config | Built-in defaults: port 8080, loopback, no databases |
| The Docker image | /app/volca.toml inside the image, unless you mount your own and point --config at it |
| The desktop app | The bundled file, with your fragment appended (see below) |
| A hosted engine | Written for you; [hosting] is what its operator sets |
The file is read once, at startup. A change takes effect when the engine restarts: there is no reload. On a hosted engine you do not restart anything yourself, the idle timeout does it.
Three settings can be overridden without touching the file, which is what a container or a service unit uses. Each override wins over the file, except where noted:
| Setting | Overridden by |
|---|---|
[server] port | --port |
[server] password | --password. VOLCA_PASSWORD is read only when neither the flag nor the file sets one, so it cannot rotate a password written in the file |
| Which databases load at startup | --load name1,name2 |
Two environment variables act on paths rather than settings. VOLCA_DATA_DIR is the engine’s write root, where uploaded databases and their caches are kept. It also redirects any reference-data path that begins with data/ to that directory, which is how a configuration of your own can point into the data bundle a release ships. VOLCA_URL tells the command-line client which server to talk to, instead of deriving one from [server].
Desktop app
Section titled “Desktop app”The desktop app ships its own volca.toml inside the application bundle; do not edit that copy. To extend it, create a volca.toml in the app’s data directory:
| Platform | Path |
|---|---|
| Linux | ~/.local/share/com.volca.desktop/volca.toml |
| macOS | ~/Library/Application Support/com.volca.desktop/volca.toml |
| Windows | %APPDATA%\com.volca.desktop\volca.toml |
At every launch the app appends this fragment to a copy of the bundled configuration, written beside it as .volca-assembled.toml, and starts the engine on that copy. The fragment can only add entries: [[classification-presets]] of your own, [[databases]] or [[methods]] read from disk, or extra reference data files. Use absolute paths; a relative path resolves against the data directory above.
Three things to know:
- The fragment must start with a
[section]or[[section]]header, and every line in it has to sit under one. A top-level key cannot go in a fragment at all: appended below the bundled part’s sections, TOML would read it as a member of the last one. The app refuses a fragment that opens with a bare key, but that check reads the first line only, so a bare key further down is still swallowed. - Pick names that the bundled configuration does not already use. Startup refuses a duplicated database, method-collection or preset name. Labels are not checked, so two presets may share one and the dropdown will show it twice.
- A TOML mistake stops the engine and the loading screen shows the parse error. Its line numbers refer to
.volca-assembled.toml, whose tail is your fragment: open that file to find the line.
Reference data
Section titled “Reference data”Three top-level keys name single CSV files. geographies has a built-in default, the hierarchy the engine was built with, and a file replaces it. The other two are optional; without one, the engine does without that table rather than substituting a default.
geographies = "data/geographies.csv" # region codes, display names, parentschem-synonyms = "data/chem_synonyms.csv" # PubChem snapshot for the mapping suggestersubstance-edges = "data/substance_edges.csv"| Key | What it does |
|---|---|
geographies | The location hierarchy a regionalized characterization factor cascades through, the geography a cross-database link resolves against, and the region names and parents list_geographies reports. Unset, the built-in hierarchy applies |
chem-synonyms | Widens the flow-mapping suggester’s recall. See Flow Mapping Audit |
substance-edges | Typed correspondences between substances, used when a flow and a characterization factor name the same thing differently |
Server
Section titled “Server”[server]port = 8080host = "127.0.0.1" # the interface to listen onpassword = "secret" # optional - guards /api/ and /mcpname = "lab-archive" # optional - how this server introduces itself over MCP| Field | Default | Description |
|---|---|---|
port | 8080 | HTTP port. --port overrides it; --port 0 takes a free port from the operating system, on loopback |
host | 127.0.0.1 | The interface to listen on. The default answers this machine and nothing else, which is what leaving the password unset assumes. "0.0.0.0" answers the network over IPv4, "::" over IPv6, and a specific address answers on that interface alone |
password | – | If set, every request to /api/ and /mcp must carry it: Authorization: Bearer <password>, HTTP Basic, or the session cookie the login page sets. Static files and the login page stay public. --password wins over this field; VOLCA_PASSWORD is only read when neither is set |
name | – | If set, the MCP handshake announces it, so an assistant connected to several VoLCA servers can say which one answered |
Databases
Section titled “Databases”Each database is a [[databases]] entry. The format is detected from the file extension and contents – there is no format field.
[[databases]]name = "ecoinvent" # identifier used in the --db flagpath = "/data/ecoinvent-3.10" # directory or archive with the data filesload = true # load at startup
[[databases]]name = "agribalyse"path = "/data/agribalyse4.csv"load = truedepends = ["ecoinvent"] # cross-database linking (flows resolved against ecoinvent)| Field | Default | Description |
|---|---|---|
name | required | Unique identifier; used as --db NAME in the CLI |
path | required | Directory or file path to the data |
displayName | name | Human-readable name |
description | – | A sentence about this database, shown where it is listed |
load | false | true loads it at startup, along with everything it depends on. false leaves it configured but not in memory |
default | false | Marks this as the default database. At most one may |
depends | none | Database names to resolve unlinked suppliers against |
deletable | false | Whether this database may be deleted at runtime. Uploaded databases are deletable; one named here is background data the whole installation shares, so it is not unless you say so |
allocation | declared | How a process with several products is divided between them. declared keeps the shares the source states. wet mass weighs each product by the wet mass its line states, or by its own amount where that amount is already a mass. dry mass weighs by a stated dry mass only, since nothing in a wet kilogram says how much of it is water. The space in the two-word values is part of them. A process the key cannot weigh is not divided at all and has no column in the matrix, the same refusal a process stating no share gets; the load says how many there were, names ten of them, and why |
geography_policy | global | How a supplier is chosen when none is available in the region asked for: exact refuses to substitute, parent walks up the geography hierarchy, global falls back to a global dataset |
locationAliases | none | A table of corrections applied to this database’s location codes, written wrong = "right" |
patches | none | Corrections to the amounts this database states, applied as it is read. See Patching the amounts a database states |
[[databases]]name = "regional-study"path = "/data/study.csv"geography_policy = "parent"
[databases.locationAliases]"FR " = "FR""Europe (RER)" = "RER"Patching the amounts a database states
Section titled “Patching the amounts a database states”A source file sometimes states more than the study you read it for models: a substance withdrawn from use, a treatment left out on purpose. Rather than rewrite the file, declare the correction in the configuration. A [[databases.patches]] entry belongs to the [[databases]] entry written above it, so keep the two together:
[[databases]]name = "orchards"path = "/data/orchards.csv"
[[databases.patches]]description = "the preservative these poles are no longer treated with"match = { product-name-contains = "wooden poles", flow-name-contains = "creosote" }set-value = 0.0
[[databases.patches]]description = "half the dose on French fields"match = { location = "FR", flow-name = "Acetamiprid" }scale = 0.5A patch picks exchanges by the process they belong to and by the flow exchanged. Every selector it sets must match, and it must set at least one:
| Selector | Matches |
|---|---|
activity-name-contains | Part of the activity’s name, whatever the letter case |
product-name-contains | Part of the name of the product the process makes, whatever the letter case |
location | The process’s location, exactly as the source writes it |
flow-name | The name of the flow exchanged, exactly |
flow-name-contains | Part of that name, whatever the letter case |
A part of a name left blank is refused at startup, since every name contains it. A SimaPro export often leaves the activity name empty and writes the whole designation in the product name, so product-name-contains is the selector that reads the same across formats.
The patch then multiplies the amount (scale) or replaces it (set-value), exactly one of the two. It reaches the inputs, the emissions, the waste sent to treatment and the avoided products of a process, never the rows saying what the process makes: those define the unit every other amount is stated per. A set-value is written in the unit the engine records the row in, which for SimaPro CSV and Brightway Excel is the reference unit of its dimension (a row written in grams is recorded in kilograms). A process with several products is divided before patches apply, so a set-value lands whole on each of the processes it was divided into.
The load says how many exchanges each patch touched, naming the patch by its description (or, without one, by its selector), and warns about a patch that touched none, which is most often a misspelt name. A database whose list of patches changed is rebuilt from its source rather than read from its cache. A database derived or copied from a patched one reads the same files, and takes its patches from it.
Supported formats
Section titled “Supported formats”| Format | Extension / structure |
|---|---|
| EcoSpold 2 | Directory or .7z/.zip of .spold files |
| EcoSpold 1 | .xml file (older format) |
| SimaPro CSV | .csv or .csv.zip file |
| ILCD | Directory with processes/ and flows/ subdirectories |
| Brightway Excel | Single .xlsx file (Brightway/bw2io layout) |
Methods (LCIA)
Section titled “Methods (LCIA)”A [[methods]] entry is a collection of characterization methods read from one file or directory. Four shapes are accepted: an ILCD method package (directory or .zip of XML), a SimaPro CSV method export, a columnar method CSV, or an openLCA ImpactCategory JSON-LD document.
[[methods]]name = "EF3.1"path = "/data/EF3.1" # directory or .zip of ILCD method XML, or a SimaPro CSV exportactive = true # default truedescription = "JRC ILCD EF 3.1"global-methods = ["Land use"]| Field | Default | Description |
|---|---|---|
name | required | Identifier for this collection |
path | required, except for a built-in collection | File or directory holding the methods |
active | true | false keeps the collection configured but unloaded |
description | – | A sentence about this collection |
global-methods | none | Names of methods in this collection whose location-specific factors are dropped, so every flow falls back to the method’s own unlocated factor. A method whose factors are all region-tagged is left with none, and scores zero |
A collection can also carry single-score definitions and corrections to individual factors, in [[methods.scoring]] and [[methods.patches]]. Both are described on their own page: Single score.
One collection is built into the engine and loaded beside the ones a configuration lists: plain-indicators, which counts raw physical quantities through the supply chain (land occupied, water used, fossil CO2, methane, primary energy, waste heat, cadmium), every factor equal to 1. An entry with that name and a path replaces it with a file. An entry with that name and no path keeps it, and can switch it off or give it single scores and corrections:
[[methods]]name = "plain-indicators"active = false # switched offAny other entry without a path is refused at startup.
Reference data tables
Section titled “Reference data tables”Three arrays name CSV files that help the engine recognise the same thing under different names, and one bridges units. All four tables are built into the engine and active by default, as Default flow synonyms, Default compartment mapping, Default units and Default energy densities. A configuration that says nothing about a kind runs on the built-in table; one that lists entries for a kind gets exactly what it lists, so a file of your own stands beside the built-in table only when both are named. Name the built-in with a path to replace it with a file, or with no path and active = false to switch it off:
[[flow-synonyms]]name = "my-synonyms"path = "/data/my-synonyms.csv"
[[flow-synonyms]]name = "Default flow synonyms" # kept beside the file above
[[compartment-mappings]]name = "Default compartment mapping"path = "/data/compartments.csv" # replaced by a file of your own
[[units]]name = "Default units"path = "/data/units.csv"
[[energy-densities]]name = "Default energy densities"active = false # switched offAll four take the same fields: name, path, active (default true) and an optional description. An entry with no path can only name the built-in table of its array; any other name without a path is refused at startup.
| Array | What it does |
|---|---|
[[flow-synonyms]] | Pairs of names for the same biosphere flow, so a database and a method that spell it differently still meet. One name1,name2 pair per row, with an optional direction, CAS number and note |
[[compartment-mappings]] | Which compartments are one place written two ways (same), and which place a flow reads when the method’s collection never writes its own (if_absent). See Flow Mapping |
[[units]] | Unit conversion factors |
[[energy-densities]] | The density bridge: how much energy a kilogram of a fuel carries, and how much volume it takes. Without it, a factor written per MJ or per m³ can never meet a flow measured by mass, and those categories score zero without saying so |
A unit is read against the spellings its table holds, and the case is part of
the spelling. An exact spelling is taken in silence; a spelling that differs
from exactly one entry by case alone is taken too, and the load says which entry
it was read as, so KWH loads and reports that it is written kWh; and a
spelling that could equally be two entries stops the load and names both, since
nothing in the data says which is meant and mj against a table holding mJ
and MJ is a billion apart either way. An unknown unit warns and lets the load
continue. A table that spells one unit twice is refused at startup rather than
keeping one row and dropping the other in silence.
Hosting limits
Section titled “Hosting limits”[hosting] is for an engine serving people who did not configure it. It is the only section whose settings are refusals rather than data, and every one of them is reported by GET /api/v1/hosting, so a client can explain the limit before a request runs into it.
[hosting]read_only = trueread_only_message = "This shared engine is read-only. Create your own to upload."max_uploads = 2max_upload_mb = 100max_loaded_uploads = 1api_access = trueupgrade_upload = "Create your own engine to upload a database of your own"| Field | Default | Description |
|---|---|---|
read_only | false | true refuses everything that changes state: loading, unloading, uploads, deletes, edits, and the shutdown endpoint. Queries still answer |
read_only_message | the built-in sentence | The refusal in your own words, repeated on every surface that refuses |
max_uploads | -1 | How many databases may be uploaded. -1 is unlimited, 0 disables uploading |
max_upload_mb | 100 | Size ceiling per upload, in megabytes. -1 is unlimited, 0 disables uploading. The default applies once [hosting] exists; with no [hosting] at all there is no ceiling |
max_loaded_uploads | -1 | How many uploaded databases may be in memory at once. A machine limit rather than an offer limit: loading one more asks you to unload another |
api_access | true | Reported for whatever fronts the engine to enforce. The engine itself refuses nothing on it, so on a standalone server this setting only describes an intention |
upgrade_upload | – | What to say instead of a bare refusal when an upload is refused |
upgrade_api | – | The same, when programmatic access is refused |
upgrade_vm_size | – | The same, when the machine is short of memory |
An empty message means the engine falls back to its own sentence, which says what would clear the way rather than pointing at a plan to buy.
Classification presets
Section titled “Classification presets”Named filter shortcuts that appear in the Activities search dropdown. Each preset applies one or more { system, value, mode } classification filters.
[[classification-presets]]name = "food"label = "Food products"description = "Everything under the two food branches of ISIC"filters = [ { system = "ISIC rev.4 ecoinvent", value = "01", mode = "contains" }, { system = "ISIC rev.4 ecoinvent", value = "10", mode = "contains" },]label defaults to name, and mode defaults to "exact"; the other value is "contains".
Complete example
Section titled “Complete example”[server]port = 8080host = "127.0.0.1"
[[databases]]name = "ecoinvent"path = "/data/ecoinvent-3.10-cutoff"load = true
[[databases]]name = "agribalyse"path = "/data/agribalyse4.csv"load = truedepends = ["ecoinvent"]
[[methods]]name = "EF3.1"path = "/data/EF3.1"active = true