BerriAI/litellm · error · HTTPException

Only admins or team admins can create projects. Your role is

Error message

Only admins or team admins can create projects. Your role is {user_api_key_dict.user_role}

What it means

Project creation is restricted to proxy admins or admins of the target team. _check_user_permission_for_project resolves the caller from the virtual key and passes only for proxy-admin roles or users listed with an admin role in the team's members_with_roles. On failure the endpoint returns 403 and echoes the caller's user_role so you can see exactly why the check failed.

Solutions

  1. Retry with the proxy admin master key
  2. Add the caller's user_id to the team as an admin first (POST /team/update with members_with_roles containing an admin entry for that user), then retry
  3. Inspect the role echoed in the message: internal_user/app_user means the key belongs to a non-admin account — issue the key from an admin user instead
  4. Double-check data.team_id: being admin of team A grants no rights to create projects under team B

Example fix

# before (403 with team-member key)
httpx.post(base + '/project/new', headers=team_member_hdr, json={'team_id': 'team-a', 'project_name': 'billing'})

# after: make the user an admin of the team, then create
httpx.post(base + '/team/update', headers=admin_hdr, json={'team_id': 'team-a', 'members_with_roles': [{'role': 'admin', 'user_id': 'user-123'}]})
httpx.post(base + '/project/new', headers=user_123_hdr, json={'team_id': 'team-a', 'project_name': 'billing'})
Defensive patterns

Strategy: try-catch

Try / catch

try:
    r = httpx.post(base + '/project/new', headers=hdr, json=payload)
    r.raise_for_status()
except httpx.HTTPStatusError as e:
    if e.response.status_code == 403 and 'Only admins or team admins' in e.response.text:
        role = extract_role(e.response.text)  # message echoes user_role
        raise PermissionError(f'caller role {role} cannot create projects for team {payload["team_id"]}; use an admin of that team') from None
    raise

Prevention

When it happens

Trigger: POST /project/new where data.team_id names a team whose members_with_roles does not include the key's user as an admin, or where the key maps to a non-admin user (e.g. user_role internal_user or app_user).

Common situations: Automating project setup with a team-member or service key; platform-team-owned teams that other teams try to create projects under; SSO role mapping that assigns internal_user to the caller, which never passes this check.

Understand the failure class

Background: "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at enterprise/litellm_enterprise/proxy/management_endpoints/project_endpoints.py:412

        team_object = await _validate_team_exists(team_id=data.team_id, prisma_client=prisma_client)

        # Validate project limits against team limits
        _check_team_project_limits(
            team_object=LiteLLM_TeamTable.model_validate(team_object.model_dump()),
            data=data,
        )

        # Check if user has permission to create projects for this team
        # only team admins can create projects for their team
        has_permission = await _check_user_permission_for_project(
            user_api_key_dict=user_api_key_dict,
            team_id=data.team_id,
            prisma_client=prisma_client,
            team_object=LiteLLM_TeamTable.model_validate(team_object.model_dump()),
        )

        if not has_permission:
            raise HTTPException(
                status_code=403,
                detail={
                    "error": f"Only admins or team admins can create projects. Your role is {user_api_key_dict.user_role}"
                },
            )

        # Generate project_id if not provided
        if data.project_id is None:
            data.project_id = str(uuid.uuid4())
        else:
            # Check if project_id already exists
            existing_project = await _project_table(prisma_client).find_unique(where={"project_id": data.project_id})
            if existing_project is not None:
                raise ProxyException(
                    message=f"Project id = {data.project_id} already exists. Please use a different project id.",
                    type="bad_request",
                    code=400,
                    param="project_id",

View on GitHub (pinned to 77b7c6c40c)