AI with your own key
On Zkrinet: add the block under Building blocks in the panel — one click, your own code is untouched and a snapshot is taken first. The installation steps below apply if you run PocketBase yourself, for example after an export.
Let your app talk to an AI model — with your own API key, which never leaves the server. The app calls a single route; which provider, model and system prompt are used is decided in the admin panel. With a quota per user, a monthly budget and a log.
Supports Anthropic (Claude), OpenAI, Google Gemini and OpenAI-compatible services — e.g. Mistral (EU) or your own model in Ollama/vLLM.
Tested against PocketBase 0.40.4.
#Installation
- Copy
pb_hooks/andpb_migrations/to your PocketBase and restart. - Create one row in
zk_ai_installningar:
| Field | |
|---|---|
leverantor | anthropic, openai, gemini or kompatibel |
nyckel | Your API key (hidden — only superusers see it). Alternatively the environment variable ZK_AI_NYCKEL, which wins. Can be left empty for a local model without a key. |
modell | Required for OpenAI, Gemini and compatible. For Anthropic the default is claude-opus-5-5. |
bas_url | Only for kompatibel (e.g. https://api.mistral.ai or http://localhost:11434), or if you go via a proxy |
max_tokens | Cap on the length of the response. Anthropic: default 16,000 |
anstrangning | Anthropic only: low/medium/high — lower = faster and cheaper |
dagsgrans_per_anvandare | Questions per user per day (UTC). Default 50 |
manadsgrans_tokens | Stop all calls when the month's tokens (in + out) reach the limit. Empty = no limit — set it, it is your key that pays |
spara_innehall | Off by default: the log stores only the number of tokens, not what was asked. Only turn it on if you need it, and mention it in your privacy policy. |
- Tasks:
zk_ai_uppdragalready containsassistent. Add more (e.g.sammanfatta,oversatt) with their own system prompt, and if needed their own model andmax_tokens. The system prompts stay on the server — the app can neither read nor change them.
#Usage from the app
const r = await pb.send("/api/zk/ai/chatt", { method: "POST", body: {
uppdrag: "assistent", // optional, default "assistent"
meddelanden: [
{ roll: "anvandare", text: "How much is shipping?" },
{ roll: "assistent", text: "Shipping costs 49 kr." },
{ roll: "anvandare", text: "And to Norway?" },
],
}})
// r = { svar: "…", vagrad: false, modell: "…", tokens: { in: 412, ut: 38 }, kvar_idag: 47 }
// Show how many questions are left
const { kvar_idag } = await pb.send("/api/zk/ai/kvot")
The app sends the whole conversation every time (max 50 messages / 50,000 characters). It must begin and end with the user's message. Login is required.
| Response | Means |
|---|---|
200, vagrad: false | The answer is in svar |
200, vagrad: true | The model declined the question. Show a friendly message. |
| 400 | Error in the request (unknown task, invalid conversation) |
| 429 | Today's quota or the month's budget has run out |
| 502 | The provider responded with an error — the real error is in zk_ai_logg, the app gets a generic message |
| 503 | Not configured |
The user can read their own rows in zk_ai_logg (e.g. to show usage).
#Good to know
- Anthropic: the plugin uses API version
2026-10-21. Against Anthropic's official API, automatic fallback model is turned on for the models that support it (fallbacks: "default"): if the model declines for safety reasons, the question is re-run on another model in the same call. Not via a proxy/custombas_url. - Want to keep data within the EU? Choose
kompatibelwith an EU provider or a model you run yourself. - The quotas are counted when the question comes in. If a user sends many questions at exactly the same time, they can go a few over the limit.
#Limitations
- No streaming — the response comes when it is finished (timeout 120 s).
- Text only: no images, files or tools in this version.
- The cost is not calculated in kronor — the log shows tokens; you find the price per token at your provider.
#Test
./bank.py ai
14 tests against a fake provider (nothing reaches the internet): correct format for all four providers, fallback model only where it applies, refusals, that provider errors do not leak to the app, invalid conversations, daily quota, monthly budget, content logging off/on, permissions and key from the environment. The protections are mutant-verified.
#Who sees your requests
The block sends the text you process to the provider you chose, with your own key: Anthropic, OpenAI, Google Gemini or your own compatible service. That provider is then your provider, not ours. If the requests contain personal data you are responsible for having a data processing agreement with the provider and for where the data ends up. Zkrinet does not see the content of the requests; the log only stores token counts unless you turn on more.