Johannes Kaindl94 downloadsOne place for your LLM endpoints: URLs, models, capabilities and API tokens in the keychain — shared with other plugins through a small API.
🇬🇧 English · 🇩🇪 Deutsch
One place for your LLM endpoints: URLs, models, capabilities and API tokens in the Obsidian keychain — shared with other plugins through a small API.
If several plugins in your vault talk to language models, each one normally asks you for the same server address and the same API token. LLM Endpoint Manager is the one place where you enter them once. Tokens are never written to data.json — they live exclusively in Obsidian's keychain — and other plugins reach the endpoint list through a small API. It owns endpoints, tokens, model lists and reachability checks; it knows nothing about prompts, roles or requests, which stays with the plugins that consume the API.
chat, embedding, vision, image), default model and an on/off switch per endpoint. The order is the priority: a plugin gets the first enabled, reachable endpoint that can do what it asked for.
/v1 question, and access from a phone.Install AnySource Sideloader, add the catalog https://git.jkaindl.de/jkaindl/obsidian-catalog/raw/branch/main/catalog.json, then install LLM Endpoint Manager from it and enable it under Settings → Community plugins. Or download llm-endpoint-manager.zip from the release — it contains exactly these files — and unpack it into .obsidian/plugins/; checksums.sha256 lets you verify the download.
Download main.js, manifest.json and styles.css from the latest release, put them into <vault>/.obsidian/plugins/llm-endpoint-manager/, then enable LLM Endpoint Manager under Settings → Community plugins.
git clone https://github.com/johannes-kaindl/llm-endpoint-manager
cd llm-endpoint-manager && npm install && npm run build
# copy main.js, manifest.json and styles.css into <vault>/.obsidian/plugins/llm-endpoint-manager/
The plugin has no commands and no ribbon icon; everything happens in Settings → LLM Endpoint Manager.
| Setting | Effect | Default |
|---|---|---|
| Address | Base URL of the server. A trailing /v1 is stripped automatically. |
— |
| Label | Name shown in this tab and to other plugins. | derived from the address |
| Protocol | Wire format: OpenAI-compatible, Ollama, Automatic1111 (image), ComfyUI (image). | OpenAI-compatible |
| Capabilities | chat, embedding, vision, image — what the endpoint can do. |
chat (presets set more) |
| Enabled | Disabled endpoints are never handed to a plugin. | on |
| API token | Stored in the Obsidian keychain, never in data.json. |
none |
| Default model | Hint that plugins may use; each plugin decides for itself. | empty |
| Family (per model) | Which model family a model belongs to. | suggested from the name |
| Another spelling of (per model) | Model id that plugins send instead of this one. | empty |
| Backend (per endpoint) | Server software; Detect fills it in. | not set |
The manager keeps the endpoint list in the plugin's data.json — without tokens — and the tokens in Obsidian's keychain. A plugin that wants to talk to a model asks the manager for a capability; the manager checks reachability (cached for 30 seconds, so a dozen plugins asking about one server do not send a dozen requests), walks the list in your order and returns the first enabled, reachable endpoint with a ready-to-use base URL. It never sends prompts itself. The code layout is described in AGENTS.md.
Other plugins reach this plugin's API at app.plugins.plugins["llm-endpoint-manager"].api. Read the API object fresh on every call — never cache it — the plugin can be disabled at any time, in which case app.plugins.plugins["llm-endpoint-manager"] is undefined.
The contract is vendored into src/vendor/kit/endpoint-source.ts (from obsidian-kit's endpoint-source module, re-exported unchanged by src/core/api-types.ts) — see that file for the full LlmEndpointManagerApi interface (version, list, get, resolve, materialize, models, importEndpoints, on), the ApiEndpoint/ResolvedEndpoint/ImportResult shapes and the ApiErrorCode union. It also exports LLM_ENDPOINT_MANAGER_PLUGIN_ID ("llm-endpoint-manager"), the id this plugin registers itself under in app.plugins.plugins[...].
Provider is "openai" | "ollama" | "a1111" | "comfy", Capability is "chat" | "embedding" | "vision" | "image".ApiError instead of throwing — callers check "error" in result rather than wrapping every call in try/catch.list/get return a snapshot for display; they never carry a token.resolve(capability) finds the first enabled, reachable endpoint offering that capability (in configured order) and returns it materialized — the fastest way for a caller that just wants "something that can do chat".materialize(id) resolves one specific endpoint the caller already knows about (e.g. one the user picked explicitly).models(id) lists the models a specific endpoint currently reports (cached until the endpoint changes or force: true is passed — the list does not expire on its own; resolve()/ materialize() use a separate 30 s reachability cache, so twelve plugins asking about the same server don't send twelve requests).importEndpoints lets a caller (e.g. a migration from an older, plugin-local endpoint list) add or merge endpoint configs in bulk, including their secret values.on("changed", cb) subscribes to endpoint list changes and returns an unsubscribe function.Tokens never cross the API. list() and get() return an ApiEndpoint with a boolean hasSecret flag only — the token value itself is not part of that shape (toApiEndpoint in src/core/api.ts builds the object field by field and never touches secretId/apiKey). Only resolve() and materialize() hand back a ResolvedEndpoint whose config carries the token — and only to a caller that explicitly asked to send a request to that endpoint, at the moment it needs it, never as part of a listing.
Issues and pull requests on GitHub. Test-driven (npm run gate); see AGENTS.md.
LICENSE).LICENSE-DOCS).Copyright © 2026 Johannes Kaindl.