RocketChat/Rocket.Chat · error · Error

error-invalid-room

error-invalid-room

Error message

error-invalid-room

What it means

POST livechat/room.onHold looks the room up by body roomId with LivechatRooms.findOneById; if no room matches, error-invalid-room is thrown before the subscription/permission check. The route itself requires the on-hold-livechat-room permission and an Enterprise license, so callers without those never reach this throw.

Source

Thrown at apps/meteor/ee/server/api/v1/omnichannel/rooms.ts:29

API.v1.addRoute(
	'livechat/room.onHold',
	{
		authRequired: true,
		permissionsRequired: ['on-hold-livechat-room'],
		validateParams: isLivechatRoomOnHoldProps,
		license: ['livechat-enterprise'],
	},
	{
		async post() {
			const { roomId } = this.bodyParams;

			type Room = Pick<IOmnichannelRoom, '_id' | 't' | 'open' | 'onHold' | 'u' | 'lastMessage' | 'servedBy'>;

			const room = await LivechatRooms.findOneById<Room>(roomId, {
				projection: { _id: 1, t: 1, open: 1, onHold: 1, u: 1, lastMessage: 1, servedBy: 1 },
			});
			if (!room) {
				throw new Error('error-invalid-room');
			}

			const subscription = await Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId, { projection: { _id: 1 } });
			if (!subscription && !(await hasPermissionAsync(this.user, 'on-hold-others-livechat-room'))) {
				throw new Error('Not_authorized');
			}

			const onHoldBy = { _id: this.userId, username: this.user.username, name: this.user.name };
			const comment = i18n.t('Omnichannel_On_Hold_manually', {
				user: onHoldBy.name || `@${onHoldBy.username}`,
			});

			await OmnichannelEEService.placeRoomOnHold(room, comment, this.user);

			return API.v1.success();
		},
	},
);

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Fetch the room fresh (livechat rooms list / rooms.info) and confirm it is an open omnichannel conversation before calling.
  2. Verify the room type is 'l' — on-hold only applies to livechat rooms.
  3. Check workspace/environment when ids come from external configuration.

Example fix

// before
await api.post('/v1/livechat/room.onHold', { roomId: rid });

// after
const info = await api.get('/v1/rooms.info', { params: { roomId: rid } });
if (info?.room?.t === 'l' && info.room.open) {
  await api.post('/v1/livechat/room.onHold', { roomId: rid });
}
Defensive patterns

Strategy: validation

Validate before calling

const info = await api.get('/v1/rooms.info', { params: { roomId } });
if (info?.room?.t === 'l' && info.room.open && !info.room.onHold) {
  await api.post('/v1/livechat/room.onHold', { roomId });
}

Try / catch

try {
  await api.post('/v1/livechat/room.onHold', { roomId });
} catch (e) {
  if (e?.response?.data?.errorType === 'error-invalid-room') {
    // drop the room from the queue; it no longer exists
  } else throw e;
}

Prevention

When it happens

Trigger: POST /api/v1/livechat/room.onHold with {"roomId": "<unknown id>"} — typo, deleted room, non-livechat room id, or an id from another workspace.

Common situations: Automation holding rooms after they closed; passing a channel/group id for a non-omnichannel room; environment mismatch between where the id was read and where the call is made.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


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