RocketChat/Rocket.Chat · error · Meteor.Error

error-invalid-user

error-invalid-user

Error message

Invalid user

What it means

Thrown by findDirectMessageRoom (apps/meteor/server/api/v1/im.ts:56) when Users.findOneById(uid) returns null: the request was resolved to a userId, but no user document with that _id exists in the `users` collection. The credentials pair (token + userId) points at an account that has been deleted or never existed.

Solutions

  1. Re-authenticate: log in as an existing user (or mint a new personal access token) and retry with the new X-User-Id / X-Auth-Token pair
  2. Confirm the user exists: GET /api/v1/users.info?userId=... as an admin
  3. If a deletion job removed the user, also revoke/invalidate that user's tokens so future calls fail with a proper auth error
  4. In tests, recreate user fixtures and re-derive tokens after every database reset

Example fix

// before — token belongs to a deleted user
const headers = { 'X-User-Id': deletedUserId, 'X-Auth-Token': staleToken };
await get('/api/v1/im.messages', { username: 'alice' }, { headers }); // error-invalid-user

// after — validate credentials once, then re-login on failure
const me = await get('/api/v1/me', {}, { headers });
if (!me.success) {
  const { data } = await post('/api/v1/login', { user, password });
  headers['X-User-Id'] = data.userId;
  headers['X-Auth-Token'] = data.authToken;
}
await get('/api/v1/im.messages', { username: 'alice' }, { headers });
Defensive patterns

Strategy: validation

Validate before calling

const me = await fetch('/api/v1/me', { headers: { 'X-Auth-Token': token, 'X-User-Id': uid } });
if (me.status === 401 || (me.ok && (await me.json()).success === false)) {
  // token/user pair no longer resolves to a live account — re-login before any im.* call
  throw new Error('credentials stale: re-authenticate');
}

Try / catch

try {
  await get('/api/v1/im.messages', { roomId });
} catch (e) {
  if (e?.body?.errorType === 'error-invalid-user' && 'method' in (e.body?.details ?? {})) {
    // userId behind the token no longer exists: drop cached credentials, re-login or abort
    await invalidateCachedCredentials();
  }
  throw e;
}

Prevention

When it happens

Trigger: The authenticated user was deleted (admin action, GDPR/retention purge) while their REST token or personal access token was still being sent; a hand-crafted X-User-Id paired with a token that no longer maps to a live user; database restored from a backup missing that user record.

Common situations: Long-lived personal access tokens or OAuth tokens outliving their accounts; test suites with stale fixture user IDs after a DB reset; user-deletion jobs that forget to revoke tokens, so clients get this error instead of a clean 401.

Understand the failure class

Background: error-invalid-user: "Invalid user" errors in Rocket.Chat — what they mean and how to fix them — this error's family across 2 libraries.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@e4b8178b20 (2026-08-18). Data as JSON: /api/errors/0d7726f345325370. Report an issue: GitHub.

Appendix: source

Thrown at apps/meteor/server/api/v1/im.ts:59

import type { ExtractRoutesFromAPI } from '../ApiClass';
import { API } from '../api';
import type { TypedAction } from '../definition';
import { addUserToFileObj } from '../lib/addUserToFileObj';
import { composeRoomWithLastMessage } from '../lib/composeRoomWithLastMessage';
import { getPaginationItems } from '../lib/getPaginationItems';

const findDirectMessageRoom = async (
	keys: { roomId?: string; username?: string },
	uid: string,
): Promise<{ room: IRoom; subscription: ISubscription | null }> => {
	const nameOrId = 'roomId' in keys ? keys.roomId : keys.username;
	if (typeof nameOrId !== 'string') {
		throw new Meteor.Error('error-room-param-not-provided', 'Query param "roomId" or "username" is required');
	}

	const user = await Users.findOneById(uid);
	if (!user) {
		throw new Meteor.Error('error-invalid-user', 'Invalid user', {
			method: 'findDirectMessageRoom',
		});
	}

	const room = await getRoomByNameOrIdWithOptionToJoin({
		user,
		nameOrId,
		type: 'd',
	});

	if (!room || room?.t !== 'd') {
		throw new Meteor.Error('error-room-not-found', 'The required "roomId" param provided does not match any direct message');
	}

	const subscription = await Subscriptions.findOne({ 'rid': room._id, 'u._id': uid });

	return {
		room,

View on GitHub (pinned to e4b8178b20)