mem0ai/mem0 · error · ValueError

Role must be either 'READER' or 'OWNER'

Error message

Role must be either 'READER' or 'OWNER'

What it means

The sync add_member() validates that role is exactly 'READER' or 'OWNER' (case-sensitive list membership) before POSTing to the project members endpoint. Any other string — including lowercase variants — raises ValueError client-side. The async twin behaves identically at project.py:862.

Source

Thrown at mem0/client/project.py:538

        """
        Add a new member to the current project.

        Args:
            email: Email address of the user to add
            role: Role to assign ("READER" or "OWNER")

        Returns:
            Dictionary containing the API response.

        Raises:
            ValidationError: If the input data is invalid.
            AuthenticationError: If authentication fails.
            RateLimitError: If rate limits are exceeded.
            NetworkError: If network connectivity issues occur.
            ValueError: If org_id or project_id are not set.
        """
        if role not in ["READER", "OWNER"]:
            raise ValueError("Role must be either 'READER' or 'OWNER'")

        payload = {"email": email, "role": role}

        response = self._client.post(
            f"/api/v1/orgs/organizations/{self.config.org_id}/projects/{self.config.project_id}/members/",
            json=payload,
        )
        response.raise_for_status()
        capture_client_event(
            "client.project.add_member",
            self,
            {"email": email, "role": role, "sync_type": "sync"},
        )
        return response.json()

    @api_error_handler
    def update_member(self, email: str, role: str) -> Dict[str, Any]:
        """

View on GitHub (pinned to 001c235229)

Solutions

  1. Pass role='READER' or role='OWNER' exactly, in uppercase
  2. Normalize your internal role strings with .strip().upper() and map non-matching roles before calling
  3. Constrain the UI/API layer to a two-option enum for roles

Example fix

# before
client.project.add_member(email="a@b.com", role="admin")

# after
role = {"admin": "OWNER", "viewer": "READER"}.get(user_role)
if role:
    client.project.add_member(email="a@b.com", role=role)
Defensive patterns

Strategy: validation

Validate before calling

ROLE_MAP = {"admin": "OWNER", "owner": "OWNER", "reader": "READER", "viewer": "READER"}
role = ROLE_MAP.get(str(raw_role).strip().lower())
if role is None:
    raise InputError("role must map to READER or OWNER")
client.project.add_member(email=email, role=role)

Type guard

def is_valid_role(v: str) -> bool:
    return v in ("READER", "OWNER")

Try / catch

try:
    client.project.add_member(email=email, role=role)
except ValueError as e:
    if "Role must be" in str(e):
        return bad_request("role must be READER or OWNER")
    raise

Prevention

When it happens

Trigger: Calling `client.project.add_member(email, role="reader")`, role="admin", role="READER " (trailing space), or role=None. Lowercase 'owner' fails too because the comparison is exact.

Common situations: Piping a role chosen from a free-text dropdown; using lowercase convention from your own RBAC system; role values read from a CSV with stray whitespace or different casing.

Related errors


AI-assisted analysis of mem0ai/mem0@001c235229 (2026-08-15). Data as JSON: /api/errors/391d559758643ad5. Report an issue: GitHub.