LiteLLM with a custom OpenAI-compatible endpoint

LiteLLM exists to stop you writing vendor-specific code. Point it at one OpenAI-compatible base URL and the same completion() call reaches every model your gateway exposes — then add a fallback so a provider outage becomes a footnote rather than an incident.

In short: LiteLLM routes any OpenAI-compatible endpoint if you prefix the model ID with openai/ and pass api_base. Its built-in cost table does not know a gateway's rates, so derive cost from returned token usage and your own rates instead.

What you need

Confirm the key works first. One command, no SDK, costs nothing:
curl https://aicomp.ai/v1/models \
  -H "Authorization: Bearer sk-your-gateway-key"

A JSON list of model IDs means the key is good. Invalid token means it was copied wrong.

Step 1 — the smallest working call

Read this one carefully, because the prefix is where most people lose twenty minutes:

import os
from litellm import completion

# LiteLLM needs the provider prefix to know which wire format to speak.
# Everything OpenAI-compatible uses "openai/", including when you point it
# at a gateway rather than at OpenAI.
resp = completion(
    model="openai/deepseek-v4-pro",
    api_base=os.environ["GATEWAY_BASE_URL"],   # https://aicomp.ai/v1
    api_key=os.environ["GATEWAY_API_KEY"],
    messages=[{"role": "user", "content": "Summarise this file."}],
    max_tokens=400,
)
print(resp.choices[0].message.content)

The openai/ prefix selects the request format, not the vendor. With api_base set, it is the format that matters — the request goes where you told it to go.

Step 2 — streaming (what every chat UI needs)

from litellm import completion

resp = completion(
    model="openai/deepseek-v4-pro",
    api_base=os.environ["GATEWAY_BASE_URL"],
    api_key=os.environ["GATEWAY_API_KEY"],
    messages=[{"role": "user", "content": "Write the migration SQL."}],
    stream=True,
)
for chunk in resp:
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="", flush=True)

Step 3 — add a fallback before you need one

Fallbacks are cheap insurance and they compound well with mixed pricing: put an inexpensive model first and promote to a stronger one only when the cheap path fails.

from litellm import completion

# Cheap model first; if it errors, retry on the stronger one.
# The caller never sees which one answered.
resp = completion(
    model="openai/deepseek-v4-flash",
    api_base=os.environ["GATEWAY_BASE_URL"],
    api_key=os.environ["GATEWAY_API_KEY"],
    messages=[{"role": "user", "content": "Explain this stack trace."}],
    fallbacks=[{"deepseek-v4-flash": ["deepseek-v4-pro"]}],
    num_retries=2,
)

Step 4 — move it into the proxy when more than one process needs it

Once several services share the endpoint, repeating api_base per call is how drift starts. The proxy takes the same values once:

model_list:
  - model_name: default
    litellm_params:
      model: openai/deepseek-v4-pro
      api_base: https://aicomp.ai/v1
      api_key: os.environ/GATEWAY_API_KEY
  - model_name: cheap
    litellm_params:
      model: openai/deepseek-v4-flash
      api_base: https://aicomp.ai/v1
      api_key: os.environ/GATEWAY_API_KEY

litellm_settings:
  drop_params: true      # ignore params the upstream model does not accept
  num_retries: 2
  request_timeout: 60

# Routes: callers ask for "default", the proxy decides what that means today.
router_settings:
  fallbacks: [{ default: ["cheap"] }]

Start it with litellm --config config.yaml --port 4000 and callers use one stable URL. Changing which model answers becomes a config edit instead of a deploy.

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

How this fails in practice

SymptomCauseFix
LLM Provider NOT providedMissing openai/ prefixPrefix every model ID used with a custom base URL
404 on /chat/completionsTrailing path doubled in api_baseEnd the base URL at /v1
400 about an unsupported parameterVendor-specific fields forwarded to a model that lacks themdrop_params: true in proxy settings
Cost logs disagree with your invoiceBuilt-in cost map does not know gateway ratesCompute cost from returned token usage and your own rates
Timeout under loadNo retry or timeout setnum_retries and request_timeout in settings

What a day of this actually costs

Fallback strategies look free until you read the invoice. Below is one heavy agent day — 200k input, 60k output — which is the shape of work these tools generate:

Cost of one heavy coding day (200k input / 60k output tokens) and a 20-day month. Rates checked 2026-09-20.
ModelVendorRate
in / out per 1M
Per dayPer month
gpt-5.6-lunaOpenAI$0.1 / $0.6$0.06$1
MiniMax-M3MiniMax$0.15 / $0.6$0.07$1
deepseek-v4-flashDeepSeek$0.22 / $0.66$0.08$2
gemini-3.7-flashGoogle$0.375 / $1.875$0.19$4
claude-haiku-4-5-20251001Anthropic$0.5 / $2.5$0.25$5
deepseek-v4-proDeepSeek$0.66 / $1.98$0.25$5
glm-5.3Zhipu$0.7 / $2.2$0.27$5
qwen3.8-maxAlibaba$1 / $3$0.38$8
claude-sonnet-5Anthropic$1 / $5$0.50$10
gpt-5.6-terraOpenAI$1 / $6$0.56$11
kimi-k3Moonshot$1.5 / $7.5$0.75$15
claude-opus-5Anthropic$2.5 / $12.5$1.25$25
Cost note. Routing by task beats routing by model reputation. A cheap model handling autocomplete and summaries while a stronger one handles refactors usually lands well below the monthly figure of running everything on the strongest row — and the split survives a price change better than any single-model commitment.

FAQ

Why does LiteLLM reject my model name without the openai/ prefix?

The prefix is how LiteLLM picks the request format and response parser. With a custom base URL and no prefix, it has nothing to match against and raises a mapping error. Every OpenAI-compatible route uses openai/.

Can I set api_base once instead of on every call?

Yes — set it per entry in the proxy model_list under litellm_params, or set the corresponding provider environment variable in the process that calls LiteLLM. Per-call arguments win over both, which is useful when two departments need different endpoints.

Does LiteLLM still track cost correctly with a custom endpoint?

It tracks what it knows. Its built-in cost map is compiled from public list prices and will not know a gateway's rate for a model it has never seen, so budget numbers can be wrong by a wide margin. Log usage.prompt_tokens and usage.completion_tokens yourself and multiply by your own rates — see per-request spend attribution.

What does drop_params actually change?

It stops LiteLLM forwarding parameters the upstream model rejects. Without it, sending a vendor-specific field to a model that does not implement it turns a working call into a 400. Set it when you route the same request shape across several vendors.

Is the proxy required?

No. It matters once more than one process — or more than one language — needs the same endpoint and key. Until then, the plain Python call above is enough and one moving part fewer.

Related

Get API access