Put the whole catalog behind one LibreChat endpoint
LibreChat is the open-source, self-hosted ChatGPT-style chat interface, and any OpenAI-compatible API can be added to it as a custom endpoint in librechat.yaml. One entry pointing at Router One puts the Claude, GPT, Gemini, Grok and DeepSeek chat models in the endpoint menu for every user on your instance, and each chat turn reaches the gateway as POST /v1/chat/completions on the configured key, so a shared deployment gets one bill you can reconcile request by request in Dashboard → Logs. The entry itself is a few lines of YAML. What decides cost and reliability is how the model list is loaded, which model writes conversation titles, whether each user brings a key, and which request options move a turn onto another endpoint. Since v0.8.7, a custom endpoint can also declare provider: anthropic and send Claude IDs to /v1/messages. Checked against LibreChat v0.8.7 (June 24, 2026) and v0.8.8-rc4 (September 23, 2026) on 2026-09-29, per LibreChat's docs and source.
Mount librechat.yaml and keep the key in .env
LibreChat reads its configuration from librechat.yaml in the project root, and the CONFIG_PATH variable points it at another path. In a Docker install, LibreChat's docs have you copy docker-compose.override.yml.example to docker-compose.override.yml and enable the bind mount below, which places ./librechat.yaml at /app/librechat.yaml in the api container. Put the key in .env as ROUTER_ONE_API_KEY and reference it from the YAML as ${ROUTER_ONE_API_KEY}, so the key stays out of the file and out of version control. Create a Router One key for this instance alone and give it a maxSpend cap: every user's turns, every title request and every agent step bill to it, and the cap is the hard stop for the whole deployment. LibreChat reads the file only at startup, so apply each change with docker compose down && docker compose up -d. It exits with an error when the file fails validation, so read the api container's log after a restart before you look anywhere else.
services:
api:
volumes:
- type: bind
source: ./librechat.yaml
target: /app/librechat.yamlConfigure LibreChat to use the Router One base URL
Add an entry under endpoints.custom and keep any endpoints you already have. name is the label users see in the endpoint menu, and apiKey reads the variable from .env. baseURL is https://api.router.one/v1: LibreChat appends /chat/completions itself unless directEndpoint is true, so don't paste a full endpoint URL. With models.fetch: true, LibreChat asks GET {baseURL}/models for the list and sends the key with it; models.default is the list it shows if that request fails, and LibreChat accepts the entry only with fetch: true or a non-empty default list. titleConvo turns on conversation titles and titleModel picks the model that writes them. The example uses a cheaper catalog ID for titles than for chat, for the reasons in the titles section below:
# librechat.yaml — add under your existing endpoints: key
endpoints:
custom:
- name: "Router One"
apiKey: "${ROUTER_ONE_API_KEY}"
baseURL: "https://api.router.one/v1"
models:
default: ["anthropic/claude-sonnet-5", "openai/gpt-5.6-sol", "deepseek-v4-flash"]
fetch: true
titleConvo: true
titleModel: "anthropic/claude-haiku-4.5"
modelDisplayLabel: "Router One"
# .env
ROUTER_ONE_API_KEY=sk-your-router-one-keyLoad the model list: fetch, or a curated default list
With fetch: true, LibreChat requests GET https://api.router.one/v1/models with the endpoint's key. Router One answers that call only when a valid key is sent, and the answer holds every catalog ID, image-generation models included, which cannot serve a chat turn. On a shared instance, a curated menu is usually better: set fetch: false and list under default only the chat IDs you want people to use, copied exactly from /models with their vendor prefix, such as anthropic/claude-sonnet-5, openai/gpt-5.6-sol, google/gemini-3.8-flash, grok-4.7 or deepseek-v4-flash. Users can then pick only what you listed, and LibreChat makes no model-list request at all; a model added to the catalog later appears once you add it to the list. Keep fetch: true when users should see new catalog models without an edit to librechat.yaml. In both modes, default is what users see when the list cannot be loaded.
Title requests and other requests nobody typed
With titleConvo: true, every new conversation triggers one extra request that asks a model for a short title. By default LibreChat starts it as soon as the first message is sent, in parallel with the answer (titleTiming: immediate). titleModel decides which model runs it, and its default is gpt-3.5-turbo, which is not in Router One's catalog, so set titleModel explicitly rather than rely on the default. current_model reuses whichever model the user picked; that is simple, but every title is billed at that model's rate. A cheaper catalog chat ID such as anthropic/claude-haiku-4.5 or deepseek-v4-flash keeps titles cheap, and titleConvo: false turns them off. In Dashboard → Logs, a title shows up as its own short request under the title model's ID, next to the turn that caused it. Features you enable elsewhere in librechat.yaml that call a model on their own, such as memory, bill the same way when they use this endpoint.
Let every user bring a key
Set apiKey: user_provided, and LibreChat asks each user for their own key in the UI the first time they select the endpoint and stores it encrypted, per its docs. Each person's requests then bill to their own Router One key, under their own maxSpend cap, and the deployment's key is not involved; filtering Dashboard → Logs by API key shows one user's traffic. With fetch: true, LibreChat loads the model list with that user's saved key (per the v0.8.7 and v0.8.8-rc4 source), and a user who has not entered a key yet sees the default list. baseURL can be user_provided as well, but then LibreChat does not forward the endpoint's configured headers to the URL the user supplies; Router One needs none.
Claude over /v1/messages: a second endpoint with provider: anthropic
The endpoint above already serves Claude IDs through Chat Completions. Since v0.8.7, a custom endpoint can instead set provider: anthropic, which sends it through LibreChat's native Anthropic client and the Anthropic Messages API. Point that entry at the host root, https://api.router.one, because the client appends /v1/messages itself, and reuse the same key variable; the client sends it as x-api-key, which Router One accepts. Router One serves Claude-family and DeepSeek V4 IDs on /v1/messages and rejects any other chat ID there with HTTP 400 must be called via /v1/chat/completions. List the IDs under default with fetch: false, as LibreChat's own example for this provider does, and give the entry its own name so users can tell the two endpoints apart. Either way, the turns appear in Dashboard → Logs under the same catalog IDs.
# A second custom endpoint for Claude IDs over /v1/messages (LibreChat v0.8.7+)
- name: "Router One (Claude)"
provider: "anthropic"
apiKey: "${ROUTER_ONE_API_KEY}"
baseURL: "https://api.router.one"
models:
default: ["anthropic/claude-sonnet-5", "anthropic/claude-haiku-4.5"]
fetch: false
titleConvo: true
titleModel: "anthropic/claude-haiku-4.5"Keep the endpoint on Chat Completions: web_search and useResponsesApi
Custom endpoints send Chat Completions. Per the v0.8.8-rc4 source, LibreChat moves a request to the Responses API on its own only for first-party OpenAI URLs, so a Router One endpoint stays on /v1/chat/completions unless addParams says otherwise. Two addParams keys do: web_search: true moves the endpoint to the Responses API and attaches a hosted web_search tool, which Router One accepts and meters but does not run itself, and useResponsesApi: true moves it outright. On /v1/responses, a Claude-family ID is rejected with HTTP 400 model '<id>' must be called via /v1/messages or /v1/chat/completions, and Gemini IDs are not served there either, so leave both keys out. dropParams removes parameters LibreChat sends by default, such as stop, user, frequency_penalty or presence_penalty; use it when a 400 in Dashboard → Logs names one of them.
Agents, image uploads and file search
LibreChat's Agents can run on custom-endpoint models, and they work through tool calls, so pick IDs whose model page lists tool calling. If the gateway answers HTTP 400 with has no chat candidate that supports requested capabilities: tool_calling, the chosen ID cannot take tool calls; switch models. An uploaded image goes to the model as image input, so use an ID whose page lists image input: a text-only ID such as deepseek-v4-flash gets HTTP 400 with has no chat candidate that supports requested capabilities: vision. File search over uploaded documents runs through LibreChat's separate RAG service, which needs an embeddings model. Router One has no embeddings endpoint (POST /v1/embeddings returns 404), so configure that service with a provider that serves embeddings and keep Router One for chat.
Check the first turns in Dashboard → Logs
After the restart, pick the Router One endpoint and a model and send a short message. Dashboard → Logs should show the turn under the exact catalog ID with its tokens, cost, status, total time and, for a streamed turn that produced output, time to first token (TTFT), plus a title request under the title model's ID when titleConvo is on. A 401 means the key did not arrive: check that .env defines ROUTER_ONE_API_KEY and that the api container was recreated after the change. A 404 whose message asks for a base URL ending in /v1 means baseURL is missing it. A request missing from Logs is a clue, not proof: it may not have reached the gateway, been refused before any model was called (a 401 for the key, a 402 for funds, a 429 from your key's or account's limits, or a 400 or 404 for the model ID or endpoint), or still be awaiting pricing, so read the api container's log too. LibreChat can also estimate costs itself from the rates you enter under tokenConfig; those figures are only as current as the numbers you type, and the billed amount is the one in Logs.
Which model ID should LibreChat send?
Copy the exact model ID from /models, preserving case, hyphens, and version suffixes; do not substitute a display name. Open its detail page and match the supported API endpoints, context window, and capabilities such as tool calling to the provider and features selected in LibreChat. A catalog listing does not mean the client can use every feature of that model. Give each client or application a dedicated API key with a maxSpend cap.
Which API protocol is LibreChat using?
OpenAI-compatible describes an interface format; it does not make Chat Completions (/v1/chat/completions), Responses (/v1/responses), and Anthropic Messages (/v1/messages) interchangeable. Check the installed client version, provider configuration, and actual request path against the model detail page and API compatibility fact sheet. A successful plain-text chat does not establish support for hosted tools, conversation state, or file-editing features.
Verify the LibreChat call in your request trace
Send a simple text request from LibreChat, then match its trace in Dashboard → Logs by time, model, and request_id: tokens, cost, latency, and status. Next, test streaming, tool calls, and multi-turn history separately. For failures, retain the actual request path, full error message, and request_id. If there is no matching log, either the request never reached the gateway (client configuration or connectivity) or the gateway refused it before calling a model — a 401 for the key, a 402 for funds, a 429 from your key's or account's limits, or a 400 or 404 for the model ID or endpoint; a streamed request that failed before any output may have no log either. The status and error message the client received tell which, so check them before attributing the error to the gateway or upstream.
FAQ
Should I use models.fetch or a static model list?
Use fetch: true when every catalog model should appear as soon as it exists: LibreChat requests GET /v1/models with the key, and the default list is only the fallback if that request fails. The fetched list includes image-generation IDs, which cannot answer chat turns. Use fetch: false with an explicit default list when you want to curate what users see, for example three chat IDs copied exactly from /models; users can then pick only what you listed.
Why does every new conversation add a second request in Logs?
That is the title request. With titleConvo: true, LibreChat asks titleModel for a short title as soon as the first message is sent. Set titleModel to a cheap catalog chat ID such as anthropic/claude-haiku-4.5, keep current_model if you want titles from the chat model itself, or set titleConvo: false. Don't leave titleModel out: its default, gpt-3.5-turbo, is not in Router One's catalog.
Can users bring their own Router One key?
Yes. Set apiKey to user_provided and LibreChat asks each user for a key in the UI and stores it encrypted, per its docs. Each person's spend then lands on their own key and wallet, under their own maxSpend cap, which is the simplest way to split a shared instance's bill. With fetch: true, the model list is loaded with each user's saved key.
Can Claude models use the native Messages API in LibreChat?
Yes, from v0.8.7. Add a second custom endpoint with provider: anthropic, baseURL https://api.router.one (the host root, because the client appends /v1/messages), the same key variable, and the Claude IDs under default with fetch: false. Router One serves Claude-family and DeepSeek V4 IDs on /v1/messages. The Chat Completions endpoint serves Claude IDs too, so this second endpoint is optional.
Why did a turn go to /v1/responses and fail with 400?
Check addParams. Per the v0.8.8-rc4 source, web_search: true or useResponsesApi: true moves a custom endpoint onto the Responses API. There, a Claude-family ID is rejected with HTTP 400 must be called via /v1/messages or /v1/chat/completions, and Gemini IDs are not served either. Remove both keys and the endpoint returns to Chat Completions, which serves every chat model in the catalog.
Can LibreChat's file search run through Router One?
Not the embeddings part. File search runs through LibreChat's RAG service, which needs an embeddings model, and Router One has no embeddings endpoint. Configure that service with a provider that serves embeddings; the chat turns that use the retrieved text still go through Router One.
Which LibreChat version is this guide for?
LibreChat v0.8.7 (June 24, 2026) and v0.8.8-rc4 (September 23, 2026), checked on 2026-09-29. provider: anthropic for custom endpoints needs v0.8.7 or later. LibreChat reads librechat.yaml only at startup, so restart the stack after every change and after an upgrade.
Which models can LibreChat use through the gateway?
Choose a current catalog model that supports both the endpoint and the features LibreChat uses. Check /models and the model detail page for the exact ID, current rates, and capabilities; a family name such as GPT or Claude is not a compatibility guarantee. A client that lists a model has only read the ID, from its own configuration or from GET /v1/models; verify an actual request too.
Models are listed, but requests fail with 400 or 404. What should I check?
Record the actual request path and error message, then check the exact model ID. A 400 can indicate invalid parameters, unsupported tools, or a model/endpoint mismatch; a 404 can indicate an incorrect path or missing resource, so it does not by itself establish that a model was retired. If the error says must be called via, use the named endpoint or select a model supported on the current endpoint. Do not add or remove /v1 or /chat/completions across all clients indiscriminately.
Does this work from Mainland China?
Yes. The gateway is reachable from Mainland China without a VPN, and the configuration is identical to the global setup.
How do I debug a 401/402/403/429?
Start from what your client received: the status, the error body and the X-Request-ID header. A 401 (missing, unknown, revoked or expired key), a 402 (wallet balance, or the key's maxSpend cap) and a 429 from your key's or account's rate limits are refused before any model is called, so they never appear in Dashboard → Logs — nor does a 400 or 404 for a wrong model ID or endpoint. For 401, check that the full key is sent and still active in Dashboard → API Keys; for 402, top up or raise the key's maxSpend; for a 403, keep the full error body. For 429, back off and retry later; a 429 that does show up in Logs came back from upstream after the gateway's retries. Keep the request_id and follow the error-codes reference.