langgenius/dify · error · WorkspacesLimitExceeded

limit_exceeded

limit_exceeded

Error message

Unable to create workspace because the maximum workspace limit was exceeded

What it means

Raised in POST /console/api/login (HTTP 400, code limit_exceeded) when the authenticated account belongs to zero tenants AND workspace creation is allowed by config but the license's workspaces quota is exhausted (FeatureService.get_license().workspaces.is_available() is false). Maps to WorkspacesLimitExceeded.

Solutions

  1. Upgrade the license or plan to raise the workspace quota.
  2. Have an admin invite the user to an existing workspace instead of creating a new one.
  3. Delete unused workspaces to free a slot, then retry login.
  4. Confirm FeatureService.get_license().workspaces reflects the current license; refresh/re-apply the license key.

Example fix

# before
if (FeatureService.is_workspace_creation_allowed()
        and not FeatureService.get_license().workspaces.is_available()):
    raise WorkspacesLimitExceeded()
# after - distinguish code/message so the client can react
ws = FeatureService.get_license().workspaces
if FeatureService.is_workspace_creation_allowed() and not ws.is_available():
    raise WorkspacesLimitExceeded(description=f'Workspace limit ({ws.limit}) reached; contact admin to be invited.')
Defensive patterns

Strategy: fallback

Validate before calling

null

Type guard

null

Try / catch

from controllers.console.error import WorkspacesLimitExceeded
try:
    do_login()
except WorkspacesLimitExceeded:
    prompt('Workspace limit reached. Ask an admin to invite you to an existing workspace.')

Prevention

When it happens

Trigger: POST /console/api/login with valid credentials for an account that has no tenants, where is_workspace_creation_allowed() is true and the license/workspace entitlement has no available workspace slots. Common when license seats exist but workspace count is capped.

Common situations: Self-hosted or cloud plan with a workspace cap that has been reached; expired or downgraded license reducing the workspace quota; orphaned account whose tenant membership was removed and now cannot create a new workspace; trial license limit hit.

Related errors


AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12). Data as JSON: /api/errors/ac52b06915b638b2. Report an issue: GitHub.

Appendix: source

Thrown at api/controllers/console/auth/login.py:172

                    )
                    raise InvalidEmailError()
            account = _authenticate_account_with_case_fallback(
                request_email, normalized_email, req_data.password, invite_token
            )
        except services.errors.account.AccountLoginError:
            _log_console_login_failure(email=normalized_email, reason=LoginFailureReason.ACCOUNT_BANNED)
            raise AccountBannedError()
        except services.errors.account.AccountPasswordError as exc:
            AccountService.add_login_error_rate_limit(normalized_email)
            _log_console_login_failure(email=normalized_email, reason=LoginFailureReason.INVALID_CREDENTIALS)
            raise AuthenticationFailedError() from exc
        tenants = TenantService.get_join_tenants(account, session=db.session())
        if len(tenants) == 0:
            if (
                FeatureService.is_workspace_creation_allowed()
                and not FeatureService.get_license().workspaces.is_available()
            ):
                raise WorkspacesLimitExceeded()
            else:
                return SimpleResultOptionalDataResponse(
                    result="fail",
                    data="workspace not found, please contact system admin to invite you to join in a workspace",
                ).model_dump(mode="json")

        token_pair = AccountService.login(account=account, session=db.session(), ip_address=extract_remote_ip(request))
        AccountService.reset_login_error_rate_limit(normalized_email)

        # Create response with cookies instead of returning tokens in body
        # response-contract:ignore cookie-bearing Flask response
        response = make_response(
            SimpleResultOptionalDataResponse(result="success").model_dump(mode="json", exclude_none=True)
        )

        set_access_token_to_cookie(request, response, token_pair.access_token)
        set_refresh_token_to_cookie(request, response, token_pair.refresh_token)
        set_csrf_token_to_cookie(request, response, token_pair.csrf_token)

View on GitHub (pinned to ef8544b173)