RocketChat/Rocket.Chat · error · Error

invalid-room

Error message

invalid-room

What it means

Thrown by GET /api/v1/livechat/room?token=...&rid=... when LivechatRooms.findOneOpenByRoomIdAndVisitorToken(rid, token) returns null. Unlike the message endpoints' findRoom, this lookup only matches OPEN rooms — so both a wrong/mismatched rid AND an already-closed room produce 'invalid-room' here.

Solutions

  1. Omit rid and call GET livechat/room?token=... alone — it returns the currently open room or creates a new one, handling the closed-room case gracefully.
  2. On 'invalid-room' here, treat the conversation as ended: clear cached rid and start a fresh room.
  3. Confirm rid actually belongs to this token (ownership mismatch gives the same error).

Example fix

// before (resume with a stale rid that may be closed)
const res = await fetch(`/api/v1/livechat/room?token=${token}&rid=${cachedRid}`);

// after (drop rid on failure and let the server return/create the open room)
let res = await fetch(`/api/v1/livechat/room?token=${token}&rid=${cachedRid}`);
let body = await res.json();
if (!body.success && body.error === 'invalid-room') {
  res = await fetch(`/api/v1/livechat/room?token=${token}`);
  body = await res.json();
}
const room = body.room; // may be a new room (body.newRoom === true)
Defensive patterns

Strategy: fallback

Validate before calling

// Prefer the rid-less call, which returns or creates the open room
const res = await fetch(`/api/v1/livechat/room?token=${encodeURIComponent(token)}`);
const body = await res.json();
if (!body.success) throw new Error(`room resolve failed: ${body.error}`);
const rid: string = body.room._id; // fresh room when body.newRoom === true

Try / catch

if (isLivechatErrorResponse(body) && body.error === 'invalid-room') {
  // rid closed or mismatched: clear cache and fall back to rid-less resolve (creates a new room)
  cachedRid = undefined;
  const fresh = await (await fetch(`/api/v1/livechat/room?token=${encodeURIComponent(token)}`)).json();
  handleRoom(fresh.room, fresh.newRoom);
}

Prevention

When it happens

Trigger: GET livechat/room with rid of a room that was closed (visitor closed it, agent closed it, or closeRoom/closeLivechatRoom ran) or rid belonging to a different visitor token. Calling with rid after the conversation ended is the classic case.

Common situations: Widget resumes a session after the chat was closed server-side (SLA timeout, agent closing, visitor close earlier) and re-requests the room by id; integrations storing room ids across restarts without handling closure; race where the room closes between listing and fetching.

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/aa20d4d7643d3be2. Report an issue: GitHub.

Appendix: source

Thrown at apps/meteor/server/api/v1/omnichannel/room.ts:120

					},
				};

				const newRoom = await createRoom({
					visitor: guest,
					roomInfo,
					agent,
					extraData: extraParams as IOmnichannelInquiryExtraData,
				});

				return API.v1.success({
					room: newRoom,
					newRoom: true,
				});
			}

			const froom = await LivechatRooms.findOneOpenByRoomIdAndVisitorToken(rid, token, {});
			if (!froom) {
				throw new Error('invalid-room');
			}

			return API.v1.success({ room: froom, newRoom: false });
		},
	},
);

// Note: use this route if a visitor is closing a room
// If a RC user(like eg agent) is closing a room, use the `livechat/room.closeByUser` route
API.v1.addRoute(
	'livechat/room.close',
	{ validateParams: isPOSTLivechatRoomCloseParams },
	{
		async post() {
			const { rid, token } = this.bodyParams;

			if (!rcSettings.get('Omnichannel_allow_visitors_to_close_conversation')) {
				throw new Error('error-not-allowed-to-close-conversation');

View on GitHub (pinned to b2c16d5842)