Docs
LibreChat
LibreChat takes a gateway as a block in librechat.yaml: four required fields, an environment variable for the key, and a restart. The picker then holds Claude, GPT and Gemini ids behind one endpoint name.
LibreChat takes a gateway as an endpoints.custom block in librechat.yaml — four required fields, the key from .env, and a restart before the picker shows it.
# librechat.yaml — project root, beside your .env
version: 1.3.5 # the value the documentation's own example carries
endpoints:
custom:
# Required: name, apiKey, baseURL, models. The name must be unique and
# must not reuse a built-in endpoint name such as openAI or anthropic.
- name: "Kunavo"
apiKey: "${KUNAVO_API_KEY}" # resolved from .env, not written here
# Keep the /v1. LibreChat appends /chat/completions to this by default.
baseURL: "https://api.kunavo.com/v1"
models:
default: ["claude-sonnet-5", "claude-haiku-4-5"]
fetch: true # fills the picker from GET /v1/models
titleConvo: true
titleModel: "gpt-5-6-luna" # titles are a separate call — pin a cheap id
modelDisplayLabel: "Kunavo"
# Optional but worth the four lines: without it LibreChat prices your
# traffic from a table it ships. prompt/completion are USD per million
# tokens; context is that model's own window. All three required.
tokenConfig:
claude-sonnet-5:
prompt: 2
completion: 10
context: 1000000
claude-haiku-4-5:
prompt: 0.4
completion: 2
context: 200000baseURL keeps the /v1. The documentation settles this in prose rather than by example: it says directEndpoint exists for a base URL that is already the full completions endpoint, and that this is “necessary because the app appends ‘/chat/completions’ or ‘/completion’ to the baseURL by default”. So https://api.kunavo.com/v1 resolves to /v1/chat/completions, which is the route to hit, and directEndpoint stays unset. Both of the site's own worked examples end the same way — https://api.mistral.ai/v1 and https://openrouter.ai/api/v1. A bare origin here shows up as a 404, not an authentication error.librechat.yaml has to exist in the project root, be mounted into the API container, and that LibreChat must be restarted before the change reaches the UI. A new endpoint that never appears in the selector is almost always this and not the credentials — settle the credentials separately with the curl below.tokenConfig block above exists to override. Declare it for every id you expose, or read the ledger as an estimate and your balance at /app/billing as the fact.curl below, and the client's behaviour is between you and LibreChat.Step by step
- Create a key at
/app/keysand copy it — it is shown once. - On Docker, mount the config first: the quick start has you copy
docker-compose.override.yml.exampletodocker-compose.override.ymland uncomment thelibrechat.yamlvolume. A bare-metal install skips this step. - Create or edit
librechat.yamlin the project root — the same directory as your.env— and add theendpoints.customentry above. - Put the key in
.envasKUNAVO_API_KEY=sk-kn-.... The${KUNAVO_API_KEY}placeholder in the YAML is resolved from there, which is what keeps the secret out of the config file you commit. - Restart LibreChat, then open the endpoint selector: Kunavo appears as its own entry beside the built-in ones, with the model list either fetched from
GET /v1/modelsor taken from yourmodels.defaultarray if that fetch fails. - Send one message, then check the model picker actually switches models — the ids resolve at the endpoint, so a Claude id and a GPT id under one entry are normal here, not a misconfiguration.
Checked against LibreChat's custom endpoint object reference on September 21, 2026. Third-party settings move; if a field name here no longer matches what you see, that page is the authority, not this one.
Verify before you debug the client
One request settles whether a failure is the endpoint, the key, or the configuration file. If this returns JSON, the same base URL and key work in LibreChat.
# Settles whether a failure is the endpoint, the key, or the client.
curl -sS https://api.kunavo.com/v1/models \
-H "Authorization: Bearer sk-kn-..."Which model id to put in the field
Every text model is reachable as a model id — the live list is GET /v1/models, and the catalog with prices is on the models page. Rates are USD per 1M tokens, input / output.
| Model id | Kunavo in / out | Where it fits in LibreChat |
|---|---|---|
claude-sonnet-5 | $2.00 / $10.00 | the default entry in models.default — the everyday conversation model |
claude-opus-5 | $2.00 / $10.00 | the id you switch to for a long analysis, where a better answer is worth the turn |
claude-haiku-4-5 | $0.40 / $2.00 | a shared instance's volume traffic, where the number of turns dominates |
gpt-5-6-luna | $0.07 / $0.42 | titleModel — LibreChat titles every conversation in a separate call |
gemini-3-8-flash | $0.525 / $2.625 | long pasted documents, where the context window is the deciding factor |
Three optional fields that behave differently behind a gateway
Everything in this table is from the same field reference, read on the date above. It is LibreChat's description of its own configuration — not a Kunavo test result, and not a claim about how a particular model id behaves once the request leaves the client.
| Field | What the reference says | Why it matters for a gateway |
|---|---|---|
provider | Routes a custom endpoint through a native provider client. Anthropic is currently the supported value. | It swaps the wire protocol, not the vendor: the same block can speak Anthropic Messages instead of chat completions. The OpenAI-style model fetch is not used on that path, so list ids under models.default explicitly. |
models.fetch | When true, attempts to fetch a list of models from the API, and may cause slowdowns during initial use if the response is delayed. | Kunavo answers GET /v1/models, so the picker populates itself. models.default is the fallback when that call fails — which is why it is worth filling in even with fetch on. |
tokenConfig | Defines model-specific context windows and per-million-token rates for cost tracking and usage calculations. | Without it the ledger prices your traffic from LibreChat's shipped table, matched against an id that table was never written for. With it, the numbers in the UI are the ones you set. |
The four-step walkthrough — mount, configure, set the environment variable, restart — is on LibreChat's quick-start page for custom endpoints, which uses a gateway as its worked example.
FAQ
How do I add a custom endpoint to LibreChat?
Create librechat.yaml in the project root, beside your .env, and add an entry under endpoints.custom with the four required fields: name, apiKey, baseURL and models. The name has to be unique and must not reuse a built-in endpoint name such as openAI or anthropic. Put the credential in .env and reference it from the YAML as ${YOUR_ENV_VAR}, then restart. On Docker the file also has to be mounted into the API container through docker-compose.override.yml, and the new entry only appears in the endpoint selector after that restart.
Does the LibreChat baseURL need /v1 at the end?
Yes, for an OpenAI-compatible gateway. LibreChat's own field reference says the directEndpoint option exists for a base URL that is already the full completions endpoint, and that this is necessary because the app appends /chat/completions or /completion to the baseURL by default. So the base URL is the API root with the /v1 suffix — https://api.kunavo.com/v1 — and directEndpoint stays unset. Both examples on LibreChat's site are shaped the same way. Getting this wrong produces a 404 rather than an authentication failure, which is how you tell it apart from a bad key.
Why do LibreChat's reported costs not match what the provider charged?
Because LibreChat prices a request from a price table it ships rather than from what your provider billed, and it matches that table against your model id. A gateway id that resembles a table entry is debited at that entry's rate, and an id matching nothing at all falls back to a fixed rate. The fix is a tokenConfig block under your custom endpoint declaring prompt, completion and context for every id you expose, in USD per million tokens; LibreChat checks that override before consulting its own table. Treat the in-app ledger as an estimate and your provider balance as the record.
Can LibreChat use Claude models through a custom endpoint?
Yes, in either of two shapes. A plain OpenAI-compatible custom endpoint passes the model id straight through to your baseURL, so a Claude id resolves at that endpoint rather than inside LibreChat, and no Anthropic account is involved. Alternatively the provider field routes the same block through LibreChat's native Anthropic Messages client — anthropic is currently the supported value. On that path the OpenAI-style model fetch is not used, so list the ids you want under models.default instead of relying on fetch.
Has Kunavo tested LibreChat?
No. The configuration on this page is transcribed from LibreChat's own custom endpoint documentation on the date shown, and nothing on it is a runtime result — no conversation, no streamed turn, no tool round-trip, no Agents run. That is true of every client documented here; a published setup page is not a test. What you can settle on your own in ten seconds is whether the base URL and key work at all, which is what the curl on this page is for; everything after that is LibreChat's behaviour with the model id you chose.