Obsidian: setting up an AI plugin with your own API key
Obsidian AI plugins ask for three fields — endpoint, key, model. What goes in each, and why the first request sometimes fails with a 404.
5 min · · Updated · инструменты · obsidian
Text Generator, Copilot for Obsidian and similar plugins aren't tied to one vendor — they expose a field for a custom OpenAI-compatible endpoint. That's exactly what the gateway needs: one fetch_... key unlocks Claude, GPT and GLM, and the plugin never notices the difference, because from the outside it all looks like the same Chat Completions API.
What goes in the plugin's fields
Plugin settings usually have three fields: base API address, key, and model name.
- API Base URL / Endpoint:
https://api.zukko.pro/v1 - API Key: your
fetch_...key from zukko.pro/app/api-keys - Model:
claude-sonnet-5,gpt-5.6-sol,glm-5.3-flash— anything from the catalogue (the current list is atGET https://api.zukko.pro/v1/models)
The key is shown once, at creation time. If the plugin asks you to paste it again and you've already closed the dashboard tab, issuing a new key is faster than trying to recall the old one.
The most common cause of a 404: a doubled /v1
Some plugins append /chat/completions to whatever you type in the endpoint field on their own. If that field holds https://api.zukko.pro/v1 and the plugin adds another /v1 before the path, the request lands on a path that doesn't exist and comes back 404. The easiest way to see exactly how a plugin builds the URL is Obsidian's network log (Ctrl+Shift+I → Network) — the final request URL is right there.
If you're unsure, test the key on its own, outside the plugin:
curl https://api.zukko.pro/v1/chat/completions \
-H "Authorization: Bearer fetch_your_key" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-5.6-sol","messages":[{"role":"user","content":"hello"}]}'If curl works but the plugin doesn't, the issue is in how the plugin assembles the address, not in the key.
Which model for which task
For note summarization and auto-tagging, glm-5.3-flash ($0.04 per million tokens) is enough — it's a simple task, and paying flagship prices for it doesn't make sense. For actual writing help, try claude-sonnet-5 — and check on your own notes whether the result works for you. If you want to run the whole vault through one request — Claude and GPT models have a 1,000,000-token context (GLM has 128K), enough for hundreds of notes at once — reach for claude-opus-5-5 with extended thinking when the job is finding connections between notes, not just answering a single question.
Get the model list and prices without a key:
curl https://api.zukko.pro/v1/models/infoA separate key for Obsidian, with a limit
Plugins with auto-run features (auto-tagging on save, for instance) can quietly fire hundreds of requests in an evening if a note triggers a loop or a huge file gets picked up. A key's page in the dashboard lets you set a daily spending limit in dollars and a list of allowed models — it's worth giving Obsidian its own key with a modest limit instead of reusing the one you use for everything else.
Caching repeated questions
If you keep asking the same thing about the same note (say, "shorten this" for a note that hasn't changed since last time), identical requests are cached for up to 7 days. The primary way to turn the cache on is the X-Zukko-Cache: true header (if the plugin lets you add custom headers): it works for any model. temperature: 0 also enables the cache, but only for models that accept that value — newer Claude models (claude-sonnet-5, claude-opus-5-5) may reject a temperature other than 1 with a 400, so for them it's best to leave the plugin's temperature field empty. The cache only hits on an exactly identical request body: edit even one paragraph of the note and it's a new request. A repeated question with the same text comes back instantly and free, the response carries X-Zukko-Cache: hit, and such hits are not written to /v1/usage. The plugin UI won't show that header, but when debugging with curl a cache hit is obvious — the second identical request answers noticeably faster than the first.
Reading the common errors
- 401 — key rejected: check for a stray space from copy-pasting, and make sure the field is actually
Authorization/API Key, not something else. - 402 — empty balance:
{"error":{"message":"Insufficient balance","code":"insufficient_balance"}}. Balance is checked before the request reaches the model, so a failed attempt costs nothing. - 404 model_not_found — a typo in the model name:
Claude-Sonnet-5is not the same asclaude-sonnet-5, case matters. - 429 key_daily_limit_reached — that key's daily limit tripped; either wait for it to reset (at 00:00 UTC) or raise the limit on the key's page. The gateway has no rate limit of its own: any other 429 comes from the provider.
Tracking spend
GET /v1/balance with the same auth header shows your remaining balance and total spend. GET /v1/usage returns a paginated history of charges (cursor-based, via next_cursor): each record has created_at, model, input_tokens, output_tokens, total_tokens and cost_cents (fractional cents). The history covers the whole account — all keys together, with no per-key filter, and the balance belongs to the account too. To see the plugin's spend separately from other projects, log the X-Zukko-Cost-Micros header yourself (only non-streaming responses carry it) or give Obsidian a separate account.
If a model is temporarily unavailable
Every so often a specific model gets pulled from the catalogue for a while — the response comes back with 503 provider_unavailable or model_disabled. That's not the plugin breaking or a key problem, just a temporary state for that one model. If the plugin can't switch to a fallback on its own, the simplest fix is to manually change the "Model" field to a different one from the same family for a few minutes — say, from claude-sonnet-5 to gpt-5.6-sol — and switch back once the original is working again. Keeping a plan B in mind for this case is cheaper than figuring out why the plugin has gone quiet right when you need to shorten a long note fast.
Keep in mind that 503 model_disabled also comes back when a model is switched off for a long time, not just for a few minutes. If the error persists, check the current list — GET https://api.zukko.pro/v1/models or the Models page in the dashboard: if the model isn't there, putting the old name back won't help; the model field needs a one-time update to the current value.
zukko
Pay 25×
less
for Claude Code & ChatGPT
One fetch_* key and base URL api.zukko.pro — for Cursor, Claude Code, Codex, and any SDK.
Related articles
- opencode agents: setting up roles, models and toolsA custom agent in opencode is a role, a model and a set of permissions. A working config where a cheap model handles routine work and a stronger one edits code.
- OpenAI API Keys in the Open: Why You Should Not Use ThemWhere free keys on GitHub come from, why using them is dangerous for you and for the owner, and how to protect your own key from the same leak.
- GLM API Errors: Why the Request Fails and How to Fix ItA wrong model id, an overflowing 128K context, the key, the balance, limits: the typical causes of GLM API errors and a quick way to find yours.
- Plan mode in Claude Code: plan first, then edit the codeIn plan mode Claude Code reads your code and proposes a plan but edits nothing until you agree. How to enable it, use it and what it costs.