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
# 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: 200000
baseURL 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.
Editing the file is not enough on Docker. The quick-start page is explicit that 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.
The usage ledger is LibreChat's arithmetic, not the charge. LibreChat prices a request from a table it ships, matched against your model id, so a gateway id can be debited at a different model's rate — which is what the 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.
This configuration was read off LibreChat's own documentation on the date below. Kunavo has not run LibreChat against its endpoint — no conversation, no streamed turn, no tool round-trip, no Agents run. A published setup page is not a test, and nothing here should be read as one; what you can settle in ten seconds is the curl below, and the client's behaviour is between you and LibreChat.
Kunavo serves no embedding, text-to-speech or speech-to-text model, so this endpoint answers chat completions and nothing else. That matters here because LibreChat has side features that ask for other kinds of provider: file chat runs its vector index through a separate RAG API with its own key and base URL, and the speech features take their own credentials again. Those stay pointed at whatever provider they already use; the key in the block above is for the custom endpoint and for nothing else on the page.

Step by step

  1. Create a key at /app/keys and copy it — it is shown once.
  2. On Docker, mount the config first: the quick start has you copy docker-compose.override.yml.example to docker-compose.override.yml and uncomment the librechat.yaml volume. A bare-metal install skips this step.
  3. Create or edit librechat.yaml in the project root — the same directory as your .env — and add the endpoints.custom entry above.
  4. Put the key in .env as KUNAVO_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.
  5. 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/models or taken from your models.default array if that fetch fails.
  6. 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.

This is the short version. The full walkthrough — model choice, what a real session costs, and the failure modes — is in what LibreChat actually costs to run.

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 idKunavo in / outWhere it fits in LibreChat
claude-sonnet-5$2.00 / $10.00the default entry in models.default — the everyday conversation model
claude-opus-5$2.00 / $10.00the id you switch to for a long analysis, where a better answer is worth the turn
claude-haiku-4-5$0.40 / $2.00a shared instance's volume traffic, where the number of turns dominates
gpt-5-6-luna$0.07 / $0.42titleModel — LibreChat titles every conversation in a separate call
gemini-3-8-flash$0.525 / $2.625long pasted documents, where the context window is the deciding factor
Billing is per token from a prepaid balance with no monthly fee — see billing. On repeated context — which is most of what an editor or a chat client sends — prompt caching moves the bill more than the model choice does.

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.

FieldWhat the reference saysWhy it matters for a gateway
providerRoutes 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.fetchWhen 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.
tokenConfigDefines 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.