litellm - 💡(How to fix) Fix Virtual-key auth fails with 'relation "LiteLLM_VerificationToken" does not exist' under transaction-pooled DATABASE_URL (e.g., Neon pgbouncer, Prisma raw queries lose search_path)

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

When DATABASE_URL points at a transaction-pooled Postgres endpoint (e.g. Neon's -pooler.* hostnames, PgBouncer in transaction mode) AND the LiteLLM Prisma schema is in a non-default Postgres schema (e.g. ?schema=litellm in the connection string), all virtual-key authentication on /chat/completions fails with:

{"error":{"message":"Authentication Error, relation \"LiteLLM_VerificationToken\" does not exist","type":"auth_error","code":"401"}}

Master-key auth on /chat/completions works (it doesn't hit the raw VerificationToken query). /key/info, /key/generate, /key/update, /key/delete all work (they use the Prisma model API, which honors ?schema=). Only completions-time virtual-key auth — which goes through unqualified raw SQL — fails.

Error Message

{"error":{"message":"Authentication Error, relation "LiteLLM_VerificationToken" does not exist","type":"auth_error","code":"401"}} | Pooler host (ep-…-pooler.c-3.us-west-2.aws.neon.tech) | "$user", public ✗ | ERROR: relation does not exist ✗ |

Root Cause

Empirically verified root cause

Fix Action

Fix / Workaround

Operator workarounds (today)

If maintainers agree on the schema-qualifier approach (or prefer a different shape), I'll send a PR. The patch touches ~7 raw-SQL sites; tests in tests/proxy_unit_tests/test_user_api_key_auth.py already cover the auth path. Let me know.

Code Example

{"error":{"message":"Authentication Error, relation \"LiteLLM_VerificationToken\" does not exist","type":"auth_error","code":"401"}}

---

curl -H "Authorization: Bearer $LITELLM_MASTER_KEY" -X POST .../key/generate \
        -d '{"models":[],"max_budget":1,"key_alias":"repro"}'

---

curl -H "Authorization: Bearer $NEW_VIRTUAL_KEY" -X POST .../chat/completions \
        -d '{"model":"...","messages":[{"role":"user","content":"hi"}]}'

---

qual = self._schema_qualifier()  # '"litellm".' or ''
sql_query = f"""
    SELECT v.*, ...
    FROM {qual}"LiteLLM_VerificationToken" AS v
    LEFT JOIN {qual}"LiteLLM_TeamTable" AS t ON ...
    ...
"""
RAW_BUFFERClick to expand / collapse

Summary

When DATABASE_URL points at a transaction-pooled Postgres endpoint (e.g. Neon's -pooler.* hostnames, PgBouncer in transaction mode) AND the LiteLLM Prisma schema is in a non-default Postgres schema (e.g. ?schema=litellm in the connection string), all virtual-key authentication on /chat/completions fails with:

{"error":{"message":"Authentication Error, relation \"LiteLLM_VerificationToken\" does not exist","type":"auth_error","code":"401"}}

Master-key auth on /chat/completions works (it doesn't hit the raw VerificationToken query). /key/info, /key/generate, /key/update, /key/delete all work (they use the Prisma model API, which honors ?schema=). Only completions-time virtual-key auth — which goes through unqualified raw SQL — fails.

Empirically verified root cause

This is a manifestation of prisma/prisma#7975: under transaction pooling, Prisma's per-session SET search_path lands on a different connection than the raw query that follows. Confirmed with direct psql testing:

Connection pathSHOW search_pathSELECT count(*) FROM "LiteLLM_VerificationToken"
Direct host (ep-…c-3.us-west-2.aws.neon.tech)litellm, publicreturns row count ✓
Pooler host (ep-…-pooler.c-3.us-west-2.aws.neon.tech)"$user", publicERROR: relation does not exist

Neon's pooler explicitly resets search_path to the database default on every transaction checkout. Setting ALTER ROLE <db_user> SET search_path = <schema>, public; does NOT help: the role config takes effect on direct connections but the pooler overrides it per checkout.

Source of the unqualified raw SQL

The auth-time query is in litellm/proxy/utils.py (line ~3491 on main at time of filing). The FROM "LiteLLM_VerificationToken" AS v ... join is unqualified — under transaction pooling with ?schema=<non-default> Prisma config, it resolves to public."LiteLLM_VerificationToken" which doesn't exist.

Other unqualified raw-SQL references that hit the same bug class:

  • litellm/proxy/utils.py:3026 (view creation)
  • litellm/proxy/common_utils/reset_budget_job.py:755 (budget reset)
  • litellm/proxy/management_endpoints/key_management_endpoints.py:5182,5191 (key listing)
  • litellm/proxy/management_endpoints/internal_user_endpoints.py:1005 (user info)
  • litellm/proxy/db/create_views.py:30,46 (view existence)
  • db_scripts/update_unassigned_teams.py, db_scripts/create_views.py (admin scripts)

Reproduction

  1. Provision Postgres with the LiteLLM schema in a non-default namespace, e.g. litellm.
  2. Configure DATABASE_URL to point through PgBouncer in transaction mode, with ?schema=litellm&pgbouncer=true. (Neon's -pooler hostnames are the most common case.)
  3. Generate a virtual key with the master key:
    curl -H "Authorization: Bearer $LITELLM_MASTER_KEY" -X POST .../key/generate \
         -d '{"models":[],"max_budget":1,"key_alias":"repro"}'
  4. Issue a chat completion through the virtual key:
    curl -H "Authorization: Bearer $NEW_VIRTUAL_KEY" -X POST .../chat/completions \
         -d '{"model":"...","messages":[{"role":"user","content":"hi"}]}'
  5. Observe 401 'relation "LiteLLM_VerificationToken" does not exist'.

Proposed fix

Make raw-SQL queries schema-aware. One option that's backward compatible and opt-in:

  1. Read schema from DATABASE_URL's ?schema= param OR an explicit LITELLM_DB_SCHEMA env var. Default to no qualifier (current behavior).
  2. Introduce a helper _schema_qualifier() returning f'"{schema}".' or ''.
  3. Inject the qualifier in front of every "LiteLLM_*" reference in raw SQL — either via f-string templates or a one-line regex pass per query string.

Example for utils.py:3491:

qual = self._schema_qualifier()  # '"litellm".' or ''
sql_query = f"""
    SELECT v.*, ...
    FROM {qual}"LiteLLM_VerificationToken" AS v
    LEFT JOIN {qual}"LiteLLM_TeamTable" AS t ON ...
    ...
"""

Alternatives considered and rejected (or noted as not-quite-fixes):

  • ALTER ROLE … SET search_path = … — doesn't survive PgBouncer / Neon pooler checkouts.
  • SET LOCAL search_path prefix per query — Prisma's query_raw is single-statement; would need a $transaction wrapper around every raw query, which is a much bigger refactor.
  • Drop the pooler from DATABASE_URL — works, but loses transaction-pool multiplexing. On Neon's free tier this can cause idle Postgres connections to pin compute, defeating auto-suspend and blowing through monthly CU-hour quota.

Operator workarounds (today)

Until upstream is fixed, operators in this configuration need to either:

  • Drop -pooler from DATABASE_URL and remove &pgbouncer=true (accept the CU-burn / connection-pinning trade-off).
  • Use the master key for app→LiteLLM auth instead of a virtual key (/key/info admin auth via master key still works; per-virtual-key spend tracking is unavailable; org-level max_budget still enforced).

Happy to PR

If maintainers agree on the schema-qualifier approach (or prefer a different shape), I'll send a PR. The patch touches ~7 raw-SQL sites; tests in tests/proxy_unit_tests/test_user_api_key_auth.py already cover the auth path. Let me know.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

litellm - 💡(How to fix) Fix Virtual-key auth fails with 'relation "LiteLLM_VerificationToken" does not exist' under transaction-pooled DATABASE_URL (e.g., Neon pgbouncer, Prisma raw queries lose search_path)