BerriAI/litellm · error · ProxyException

Customer already exists, passed user_id={data.user_id}. Plea

Error message

Customer already exists, passed user_id={data.user_id}. Please pass a new user_id.

What it means

LiteLLM_EndUserTable enforces a unique constraint on user_id. When the Prisma create inside POST /customer/new fails with 'Unique constraint failed on the fields: (user_id)', the endpoint maps it to a 400 bad_request ProxyException (type=bad_request, param=user_id) telling you to pass a new user_id.

Source

Thrown at litellm/proxy/management_endpoints/customer_endpoints.py:483

                new_end_user_obj.get("object_permission"),
            )
            new_end_user_obj.pop("object_permission", None)

        ## WRITE TO DB ##
        end_user_record: Final = await _typed_table(EndUserRepository(prisma_client)).create(
            data=new_end_user_obj,
            include={"litellm_budget_table": True, "object_permission": True},
        )

        await _evict_end_user_cache_keys(_end_user_cache_keys((data.user_id,)))

        return _to_customer_response(end_user_record)
    except Exception as e:
        verbose_proxy_logger.exception(
            "litellm.proxy.management_endpoints.customer_endpoints.new_end_user(): Exception occured - %s", e
        )
        if "Unique constraint failed on the fields: (`user_id`)" in str(e):
            raise ProxyException(
                message=f"Customer already exists, passed user_id={data.user_id}. Please pass a new user_id.",
                type="bad_request",
                code=400,
                param="user_id",
            )
        raise handle_exception_on_proxy(e)


@router.get(
    "/customer/info",
    tags=["Customer Management"],
    dependencies=[Depends(user_api_key_auth)],
    response_model=CustomerResponse,
)
@router.get(
    "/end_user/info",
    tags=["Customer Management"],
    include_in_schema=False,

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Check first: GET /customer/info?end_user_id=<id> - if it returns 200, update instead of create
  2. If you meant to modify the existing customer, call /customer/update with the same user_id
  3. Make creates idempotent: derive user_id deterministically and treat 'already exists' as success

Example fix

# before
create_customer(user_id="cust-42", ...)  # 400 on the second run

# after
def ensure_customer(payload):
    r = post("/customer/new", payload)
    if r.status_code == 400 and "already exists" in r.text:
        return get("/customer/info", params={"end_user_id": payload["user_id"]})
    r.raise_for_status()
    return r
Defensive patterns

Strategy: try-catch

Try / catch

import httpx


def create_customer_idempotent(base: str, headers: dict, payload: dict) -> dict:
    r = httpx.post(f"{base}/customer/new", json=payload, headers=headers)
    if r.status_code == 400 and "already exists" in r.text:
        return httpx.get(
            f"{base}/customer/info",
            params={"end_user_id": payload["user_id"]},
            headers=headers,
        ).json()
    r.raise_for_status()
    return r.json()

Prevention

When it happens

Trigger: POST /customer/new with a user_id that already exists - duplicate submission, retry of a request whose response was lost (client timeout), or a bootstrap/seed script run a second time.

Common situations: Non-idempotent create scripts retried on flaky networks; user_id derived from emails or external CRM ids that collide across teams; re-running environment-seeding payloads in CI or after restore.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/4a8b9dc4c7b56f06. Report an issue: GitHub.