> ## Documentation Index
> Fetch the complete documentation index at: https://gomodel-feat-vision-routing.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrating from LiteLLM

> Convert a LiteLLM proxy config.yaml to GoModel with one command, run both gateways side by side, and move clients over without changing model names.

GoModel speaks the same OpenAI-compatible API as the LiteLLM proxy, so most
applications only need a new base URL and key. The work is in the gateway
config, and `gomodel migrate litellm` does most of it.

| What | How it moves |
| - | - |
| `model_list`, `router_settings`, `litellm_settings`, `general_settings` | Converted by `gomodel migrate litellm` |
| Model names clients call | Kept: every `model_name` becomes a [virtual model](/features/virtual-models) |
| Master key | Kept |
| Virtual keys | Kept: imported from the LiteLLM database by their hash ([below](#4-import-virtual-keys)) |
| Budgets, rate limits, spend | Live in the LiteLLM database: recreate them in GoModel ([below](#5-recreate-budgets-and-rate-limits)) |

## 1. Convert the config

Run a dry run first. It prints the migration report and the generated config,
and writes nothing:

```bash theme={null}
gomodel migrate litellm litellm_config.yaml
```

Then write the files:

```bash theme={null}
gomodel migrate litellm --out ./gomodel litellm_config.yaml
```

With Docker (the image runs as a non-root user, so pass yours to write into the
mounted directory):

```bash theme={null}
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD":/work \
  enterpilot/gomodel migrate litellm --out /work/gomodel /work/litellm_config.yaml
```

`--out` writes three files and refuses to overwrite them unless you pass
`--force`:

| File | Contents |
| - | - |
| `config.yaml` | Providers, virtual models, retries, timeouts, and observability settings |
| `.env` | Secrets that were inline in the LiteLLM config (API keys, master key). Created with mode `0600`; keep it out of version control |
| `MIGRATION_REPORT.md` | Providers and virtual models created, environment variables to set, and every setting changed or left behind |

`os.environ/NAME` references become `${NAME}`, so the environment you already
run LiteLLM with keeps working. `include:` files are followed.

## 2. Review the report

Read `MIGRATION_REPORT.md` before sending traffic. It has three lists:

* **Review before switching traffic**: things you must finish, such as
  guardrails, model access groups, or a Langfuse callback. GoModel guardrails
  are off until you configure them.
* **Not migrated**: settings with no GoModel equivalent, named one by one.
  Nothing is dropped silently.
* **Behavior changes**: settings that were converted but behave a little
  differently, such as routing strategies.

## 3. Run GoModel next to LiteLLM

Start GoModel with the generated files on another port. Add the variables
listed under **Environment** in the report (the ones LiteLLM already read with
`os.environ/`) to `.env`, creating it if the converter wrote none. GoModel
refuses to start while the variable `server.master_key` reads is unset. Then
send a few test requests with the models your clients use:

```yaml compose.yaml theme={null}
services:
  gomodel:
    image: enterpilot/gomodel
    ports: ["8080:8080"]
    env_file: .env
    volumes:
      - ./config.yaml:/app/config/config.yaml:ro
```

```bash theme={null}
cd gomodel
docker compose up
```

Load `.env` with Compose `env_file` (or GoModel's own `.env` loader when you
run the binary from that directory). `docker run --env-file` reads values
literally, so it would keep the quotes `.env` puts around values such as
inline JSON credentials. When you run the binary directly, a variable already
exported in your shell, such as `OPENAI_API_KEY`, wins over the same name in
`.env`.

```bash theme={null}
curl -s http://localhost:8080/v1/chat/completions \
  -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "gpt-4o", "messages": [{"role": "user", "content": "ok?"}]}'
```

`GET /v1/models` lists the same model names LiteLLM did. Then move clients
over one team at a time.

## 4. Import virtual keys

LiteLLM stores each virtual key as a SHA-256 hash in its Postgres database.
GoModel imports those hashes, so clients keep their `sk-...` keys and GoModel
never handles the keys themselves. This loop imports every key that is not
blocked or expired, with its alias, expiry, and the models LiteLLM let it call.
A team key becomes user path `/<team alias>`:

```bash theme={null}
psql "$LITELLM_DATABASE_URL" -At -c "
  SELECT json_build_object(
    'name', COALESCE(key_alias, key_name, 'litellm-key'),
    'imported_from', 'litellm',
    'secret_hash', token,
    'redacted_value', key_name,
    'expires_at', to_char(expires, 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"'),
    'user_path', '/' || regexp_replace(team, '[/:]', '-', 'g'),
    'allowed_models', to_json(models))
  FROM (
    SELECT k.token, k.key_name, k.key_alias, k.expires,
      COALESCE(t.team_alias, t.team_id) AS team,
      CASE
        WHEN k.team_id IS NOT NULL THEN COALESCE(km.m, tm.m)
        WHEN km.m IS NULL OR um.m IS NULL THEN COALESCE(km.m, um.m)
        ELSE ARRAY(SELECT unnest(km.m) INTERSECT SELECT unnest(um.m))
      END AS models
    FROM \"LiteLLM_VerificationToken\" k
    LEFT JOIN \"LiteLLM_TeamTable\" t ON t.team_id = k.team_id
    LEFT JOIN \"LiteLLM_UserTable\" u ON u.user_id = k.user_id
    CROSS JOIN LATERAL (SELECT CASE WHEN cardinality(k.models) = 0
      OR k.models && '{all-proxy-models,all-team-models}' THEN NULL ELSE k.models END) km(m)
    CROSS JOIN LATERAL (SELECT CASE WHEN cardinality(t.models) = 0
      OR 'all-proxy-models' = ANY(t.models) THEN NULL ELSE t.models END) tm(m)
    CROSS JOIN LATERAL (SELECT CASE WHEN cardinality(u.models) = 0
      OR 'all-proxy-models' = ANY(u.models) THEN NULL ELSE u.models END) um(m)
    WHERE NOT COALESCE(k.blocked, false)
      AND (k.expires IS NULL OR k.expires > now() AT TIME ZONE 'UTC')
  ) keys
  WHERE models IS NULL OR cardinality(models) > 0" |
while read -r key; do
  curl -s -X POST http://localhost:8080/admin/auth-keys/import \
    -H "Authorization: Bearer $GOMODEL_MASTER_KEY" \
    -H "Content-Type: application/json" -d "$key"
  echo
done
```

Run it against GoModel's address, with `LITELLM_DATABASE_URL` set to the
`DATABASE_URL` LiteLLM uses. It is safe to re-run: keys already imported answer
`409 auth_key_exists`.

* Model access follows LiteLLM's rules: a team key gets its own model list,
  else its team's; a personal key gets its own list narrowed by its user's.
  The result becomes the key's [allowed models](/features/users). A personal
  key that may call no model is skipped.
* GoModel checks the model a virtual model resolves to, so the import stores
  the provider models behind each name, such as `openai/gpt-4o` for `gpt-4o`.
  Import keys after GoModel runs with the converted config. If you later point
  a virtual model at other models, update the allowed models of the keys that
  use it; until then they cannot reach the new models.
* Keys blocked or expired in LiteLLM are not imported. Block a key in GoModel by
  deactivating it on the **API Keys** page.

[`POST /admin/auth-keys/import`](/advanced/admin-endpoints#importing-keys-from-litellm)
describes the request. Tokens beginning with `sk-` are matched only against
imported keys.

## 5. Recreate budgets and rate limits

LiteLLM's budgets, rate limits, and spend stay in its database. GoModel models
the same ideas on a [user path](/features/user-path) hierarchy:

| LiteLLM | GoModel |
| - | - |
| Organization / team / user | User path, such as `/acme/search-team/alice` |
| `max_budget` + `budget_duration` | [Budget](/features/budgets) on the user path (hourly, daily, weekly, monthly) |
| `tpm_limit` / `rpm_limit` | [Rate limit](/features/rate-limits) on the user path |
| `metadata.tags` | [Labels](/features/labelling) |

Create them in the dashboard or with the [admin API](/advanced/admin-endpoints).

## What changes for clients

| LiteLLM | GoModel |
| - | - |
| Base URL `http://litellm:4000` or `http://litellm:4000/v1` | Base URL must end in `/v1`: `http://gomodel:8080/v1` |
| Virtual key `sk-...` | Unchanged once [imported](#4-import-virtual-keys); new keys are `sk_gom_...` |
| `model_name` aliases | Unchanged |
| `/health/liveliness`, `/health/readiness` | [`/health` and `/health/ready`](/advanced/cli#health-probe) |
| Pass-through `/anthropic/*`, `/gemini/*`, `/vertex_ai/*` | [`/p/{provider}/*`](/features/passthrough-api); Anthropic SDKs can also use `/v1/messages` directly |
| `metadata.tags` in the request body | A tagging header, such as `X-My-Tags`; see [Labelling](/features/labelling) |
| `x-litellm-*` response headers, `/key/*`, `/team/*` management API | GoModel's [admin API](/advanced/admin-endpoints) and [usage API](/advanced/usage-api) |

## How settings map

| LiteLLM | GoModel |
| - | - |
| `litellm_params.model: provider/model` | A provider in `providers`, and the model in its `models` list |
| Deployments sharing a `model_name` | A `round_robin` virtual model, weighted by `weight`, else `rpm`, else `tpm` |
| `routing_strategy: cost-based-routing` | Virtual model `strategy: cost` |
| Other routing strategies | `round_robin` with failover (noted in the report) |
| `fallbacks`, `default_fallbacks` | A `failover` virtual model that tries the group, then its fallbacks |
| `context_window_fallbacks`, `content_policy_fallbacks` | Not converted; add error phrases to [`failover.retry_on_errors`](/features/failover) |
| `model_group_alias` | A virtual model pointing at the group |
| `openai/*` wildcards | The provider serves its whole catalog; partial wildcards become a [`model_filter`](/advanced/config-yaml#filtering-a-providers-models) |
| `input_cost_per_token`, `output_cost_per_token`, `max_input_tokens` | Model `metadata.pricing` (per million tokens) and `context_window` |
| Deployment `rpm` / `tpm` under usage-based routing | [Model rate limits](/features/rate-limits) |
| `num_retries`, `timeout` | `resilience.retry.max_retries`, `http.timeout` |
| `allowed_fails`, `cooldown_time` | Circuit breaker `failure_threshold`, `timeout` |
| `callbacks: prometheus` / `otel` | `metrics.enabled` / `opentelemetry.enabled` |
| `callbacks: langfuse` | [Langfuse over OpenTelemetry](/guides/langfuse) |
| `master_key` | `GOMODEL_MASTER_KEY` (or `server.master_key`) |
| `database_url` | Not reused; GoModel uses its own [storage](/guides/production) |

Provider names follow GoModel's environment conventions: a deployment using
LiteLLM's default key variable (`OPENAI_API_KEY`) becomes provider `openai`,
one reading `OPENAI_EU_API_KEY` becomes `openai-eu`, and each Azure deployment
becomes its own provider, such as `azure-gpt-4o-prod`.

Providers GoModel does not support yet, such as Replicate or SageMaker, are
listed under **Not migrated** in the report.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.