01Where a setting comes from
Three places, and the one further left always wins:
environment variable > config.toml > built-in defaultThe Settings screen shows you which of the three each value came from, and if an environment variable is winning, the field goes read-only and says so rather than letting you write something that will have no effect. Editing in the browser writes the file; it never touches your environment.
facetmark config pathIt is created the first time something writes to it. There is no requirement to have one — a run with no file and no variables is a valid run with all defaults.
embed_backend, embed_dim and local_embed_path decide the shape of the vector store. The screen saves them and then tells you plainly that they take effect next start.
02The four that matter
| Setting | In plain language |
|---|---|
api_key | Your key. Stored in the file, shown back to you masked, and never re-sent when you save an unrelated field. |
base_url | Where the requests go. Anything speaking the OpenAI API works, including something on your own machine. |
chat_model | Used to read pages and to answer on the Ask screen. Cheap and fast beats clever here. |
embed_model | Turns text into vectors. This is the one that decides search quality. |
Vectors from two different models are not comparable. Change it and run a rebuild, or search gets quietly worse in a way no error message will tell you about.
The Test button on the Settings screen calls chat and embeddings separately and reports them separately, because in practice exactly one of them is usually the broken one — an account with chat access and no embedding access is a very common shape.
03Eight providers, ready to paste
Put these in the file from facetmark config path, or type the same values into the Settings screen. Model names move; if one is rejected, check the provider's current list rather than trusting this page.
api_key = "sk-..."
base_url = "https://api.openai.com/v1"
chat_model = "gpt-4o-mini"
embed_model = "text-embedding-3-small"
embed_dim = 1536api_key = "sk-..."
base_url = "https://api.deepseek.com/v1"
chat_model = "deepseek-chat"api_key = "sk-..."
base_url = "https://api.moonshot.cn/v1"
chat_model = "moonshot-v1-8k"api_key = "..."
base_url = "https://open.bigmodel.cn/api/paas/v4"
chat_model = "glm-4-flash"
embed_model = "embedding-3"
embed_dim = 2048api_key = "sk-..."
base_url = "https://api.siliconflow.cn/v1"
chat_model = "Qwen/Qwen2.5-7B-Instruct"
embed_model = "BAAI/bge-m3"
embed_dim = 1024api_key = "sk-..."
base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1"
chat_model = "qwen-plus"
embed_model = "text-embedding-v3"
embed_dim = 1024base_url = "http://127.0.0.1:11434/v1"
api_key = "ollama"
chat_model = "qwen2.5:7b"
embed_model = "bge-m3"
embed_dim = 1024base_url = "http://127.0.0.1:8000/v1"
api_key = "not-used"
chat_model = "Qwen/Qwen2.5-7B-Instruct"facetmark makes one kind of request for chat and one for embeddings. Plenty of people run chat on whatever is cheapest and embeddings on whatever is best. Set base_url to the embedding provider and give the chat model a fully-qualified name, or run the embeddings locally and leave the API for chat.
04With no API at all
Embeddings can run on your own machine. No key, no network, nothing leaves the laptop:
embed_backend = "local"
local_embed_path = "BAAI/bge-m3"
embed_dim = 1024It is slower to build and it needs the model downloaded once. Search quality is good: on a 1,024-token window bge-m3 reproduces its own vector to a cosine of 0.999976 run-to-run, which is the property that matters for an index you keep rather than rebuild.
Two facets are built on a language model reading your pages: the questions a page could answer, and the topic labels. With no chat model those stay empty and you are searching on body text and full-text — still the two strongest paths, and still better than what your browser gives you.
You can also start here and add a key later. Nothing has to be thrown away; the index fills in the parts it could not build before.
05Everything else
Vectors
| Setting | In plain language |
|---|---|
embed_backend | api or local. Restart to take effect. |
embed_dim | How long each vector is. Must match what the model actually returns. Restart to take effect. |
local_embed_path | Model id or folder for the local backend. Restart to take effect. |
How hard it pushes
| Setting | In plain language |
|---|---|
request_timeout | Seconds before a call is given up on. Raise it on a slow link; lower it if a provider hangs. |
fetch_concurrency | How many pages are downloaded at once. Lower it if your network complains. |
enrich_concurrency | How many pages are sent to the model at once. This is the one to lower when you get rate-limited. |
What it is not allowed to look at
| Setting | In plain language |
|---|---|
privacy_excluded_domains | Domains never fetched and never sent anywhere. Bank, health, work intranet. The bookmark stays; only the title is indexed. |
chat_model_fallbacks | Models to try, in order, when the first one refuses. |
Once a page's text is in the database, adding its domain to the list stops future fetches but does not unremember it. Rebuild after changing the list if that matters to you.
06The errors you will actually get
| Message | What to do |
|---|---|
401 / invalid_api_key | Key wrong, or wrong provider's key for this base_url. Test on the Settings screen — it tells you which half failed. |
404 on a model name | That name is not on that endpoint. Check the provider's model list. |
429 | Rate limit. Lower enrich_concurrency and run again; finished stages are not redone. |
dim mismatch | embed_dim disagrees with the model. Fix it, restart, rebuild. |
| Chat works, embeddings 403 | Common. The account has one entitlement and not the other. Switch embed_backend to local, or point embeddings at another provider. |
unknown setting on save | A typo in a key name. The writer refuses unknown keys rather than storing something that will be silently ignored forever. |