Tabby with a Custom OpenAI-Compatible Endpoint

In short: Completion, chat and embedding are three separate slots in config.toml with nothing shared, so a working chat panel says nothing about suggestions. Completion is a fill-in-the-middle call and needs a prompt template matched to the model family; a wrong template returns tokens that contain your own prompt. The version segment in the endpoint is required by the vLLM connectors and forbidden by the llama.cpp ones.

Tabby is a self-hosted coding assistant: a server you run, plus editor plugins that talk to it. Its selling point is that the model can be local, and the second-most-common reason people run it is that the model can be someone else's. Pointing it at a gateway is a config file edit, and the file has three independent model slots.

Chat and embedding are straightforward. Code completion is the one that needs extra work, because completion is not a chat call It is a fill-in-the-middle call with a template, and a gateway that does not echo that template back produces completions that are visibly wrong rather than absent.

Everything lives in ~/.tabby/config.toml, or /data/config.toml inside the container if you deployed with Docker.

You need a key before the code below runs. Create an account, generate a key, and copy the base URL (https://aicomp.ai/v1). Create one free →
Check current rates → Free to sign up · $1 minimum top-up · No prepayment

Three slots, configured separately

SlotSectionWhat it drives
Completion[model.completion.http]inline suggestions as you type
Chat[model.chat.http]the side panel conversation
Embedding[model.embedding.http]code indexing and retrieval for chat context

Nothing is shared. A working chat slot tells you nothing about completion, and the usual symptom of an unfinished setup is a side panel that answers questions while suggestions never appear.

Each slot picks a connector with kind, and the connector decides which HTTP route is called:

# ~/.tabby/config.toml

[model.chat.http]
kind = "openai/chat"
model_name = "claude-sonnet-5"
api_endpoint = "https://gw.example.com/v1"
api_key = "sk-your-gateway-key"

[model.embedding.http]
kind = "openai/embedding"
model_name = "text-embedding-3-small"
api_endpoint = "https://gw.example.com/v1"
api_key = "sk-your-gateway-key"

[model.completion.http]
kind = "openai/completion"
model_name = "deepseek-v4-pro"
api_endpoint = "https://gw.example.com/v1"
api_key = "sk-your-gateway-key"
prompt_template = "{prefix}{suffix}"

openai/chat posts to the chat completions route and openai/completion posts to the legacy completions route. An endpoint that only implements chat completions cannot serve the completion slot through this connector, no matter how the key is set.

The prompt template, and why completion breaks without it

A code completion request is not a conversation. It asks the model to continue text from a cursor position, and Tabby expresses that with a FIM template: {prefix} is the code before the cursor, {suffix} is the code after it.

Different model families were trained on different FIM formats, and the template has to match the one your model expects. For a CodeLlama-lineage model the conventional value is "<PRE> {prefix} <SUF>{suffix} <MID>"; for a Qwen Coder model it is closer to "{prefix}{suffix}" with the model's own control tokens.

Get it wrong and the failure is quiet. The request succeeds, tokens come back, and the suggestion contains fragments of your own prompt, or repeats the line above the cursor, or ignores the code below the cursor entirely. A completion that arrives but is useless is a prompt template problem, not a connectivity problem, and it is the single most common misdiagnosis with this tool.

The version segment is connector-specific

This is the second trap, and it runs the opposite way depending on kind.

Because both are described as "the endpoint", copying a connection string from one connector's example into another connector's section produces a route that 404s. For an OpenAI-compatible gateway the openai/* connectors follow the versioned convention, so include /v1 and stop there, not the operation path.

A chat slot needs a chat model

kind = "openai/chat" tells Tabby which route and which request shape to use. It cannot make a completion model behave as a chat model. Pointing the chat slot at a base-model checkpoint returns answers that trail off or loop, and the vendor note attached to the connector says as much: use a chat model.

The same applies in reverse to completion. A chat-tuned model asked to continue code will produce something that reads fine and completes nothing.

Verifying the route

Tabby exposes the state of each connector in its own UI, which is unusual and worth using before you debug from the editor.

  1. Open Information → System. The model cards show whether each configured model connected. A card that is not connected is a config problem, not an editor problem.
  2. Confirm the connector's URL convention. vllm/* carries /v1; llama.cpp/* must not.
  3. Give completion a real prompt template matched to the model family, then check a suggestion against the code surrounding the cursor.
  4. Point supported_models at real IDs if you want to switch models from the chat UI without editing the file.
  5. Restart the server after any edit. The config file is read at start.
  6. Test in the editor last. Every failure above looks the same from inside a plugin.
[model.chat.http]
kind = "openai/chat"
model_name = "claude-sonnet-5"
supported_models = ["claude-sonnet-5", "claude-haiku-4-5", "deepseek-v4-pro"]
api_endpoint = "https://gw.example.com/v1"
api_key = "sk-your-gateway-key"

Where the tokens go

Tabby's three slots have very different shapes, and the cheapest one is not the one people worry about.

Completion fires whenever you stop typing. Each request carries the code around the cursor, and the reply is tens of tokens. That is a high-frequency, large-input, small-output pattern: a hundred or more calls in a working hour, each one maybe a few thousand input tokens.

Chat is a conversation with code context attached, so it is moderate frequency and grows with the thread.

Embedding runs in bursts when a repository is indexed, then goes quiet until the repository changes.

A day on this page's workload is 260k input and 30k output tokens, which is 5.2M input and 0.6M output across 20 working days. Because completion dominates the call count and the input side, the input rate of whichever model fills that slot sets the month.

See output vs input pricing for why the ratio matters more than the headline rate, and cheapest models by workload shape for picking per slot rather than per tool.

Per 1M input / output tokens and a 20-day month
ModelGateway rate
in / out per 1M tokens
One day20 days
gpt-5.6-terra$1.00 / $6.00$0.44$8.80
claude-sonnet-5$1.00 / $5.00$0.41$8.20
deepseek-v4-pro$0.66 / $1.98$0.23$4.62
qwen3.8-max$1.00 / $3.00$0.35$7.00
glm-5.3$0.70 / $2.2$0.25$4.96

One day = 260k input + 30k output on this page's workload. 20 days = 5.2M input and 0.6M output. Rates checked 2026-10-14 (gateway rates move with upstream promotions), so verify the current number in your dashboard before committing to a budget.

Across 221 catalogue models that publish both rates, the median output rate is 4.0× the input rate, and 69% of them (152 models) charge at least 4× more for output than input. The other 624 raw catalogue entries are dropped by cleaning, test entries, dated snapshots and non-chat variants, not for a missing rate. This is why an agent workload is decided on the output column, not the input one everyone quotes.

FAQ

FAQ

Why do I get completions that contain my own code or make no sense?

The prompt_template does not match the model family's fill-in-the-middle format. The request is succeeding; the model is being asked the wrong question. Set the template to the format your model was trained on and re-test against real code.

Does api_endpoint need /v1?

It depends on the connector. The vllm/* connectors expect it, and the llama.cpp/* connectors document that it must be absent. For openai/* connectors against a gateway, include the version segment and nothing after it.

Why does the chat panel work but suggestions never appear?

They are separate slots in config.toml. A configured chat model does not configure completion. Add the completion section, and remember that it needs a prompt template.

How do I check whether Tabby connected?

Information → System shows a card per model with its connection state. Check there before looking at the editor plugin.

Can I call two different providers for different slots?

Yes, and mixing is normal: a small local completion model for latency plus a hosted chat model for quality is a common arrangement.

Why do chat replies trail off or repeat?

The chat slot may be pointed at a completion-style model. The connector sets the route and request shape; it cannot change what the model was trained for.

Related

Get API access