RocketChat/Rocket.Chat · error · Meteor.Error

error-invalid-subscription

error-invalid-subscription

Error message

Invalid subscription

What it means

Thrown by POST /api/v1/rooms.saveDraft when Subscriptions.updateDraftByRoomIdAndUserId(rid, userId, draft, tmid) returns null, which happens when the caller has no subscription document for that room, i.e. they never joined it. Drafts are stored on the user's subscription, so non-members cannot save one. A separate earlier check returns error-message-size-exceeded when draft exceeds Message_MaxAllowedSize.

Solutions

  1. Join the room first (POST /api/v1/rooms.join or the subscription flow), then re-send the draft
  2. Verify membership with GET /api/v1/subscriptions.get?rid=... before saving
  3. Drop drafts for rooms the user left instead of erroring
  4. Confirm the rid is the room the draft belongs to, not a thread message id

Example fix

// before
await sdk.post('rooms.saveDraft', { rid: privateChannelId, draft: 'hello' }); // not a member

// after
const { subscription } = await sdk.get('subscriptions.get', { rid: privateChannelId }).catch(() => ({ subscription: null }));
if (!subscription) await sdk.post('rooms.join', { rid: privateChannelId });
await sdk.post('rooms.saveDraft', { rid: privateChannelId, draft: 'hello' });
Defensive patterns

Strategy: validation

Validate before calling

const { subscription } = await sdk.get('subscriptions.get', { rid }).catch(() => ({ subscription: null }));
if (!subscription) throw new Error(`join room ${rid} before saving a draft`);

Try / catch

try {
  await sdk.post('rooms.saveDraft', { rid, draft, tmid });
} catch (e: any) {
  if (e?.response?.data?.errorType === 'error-invalid-subscription') {
    await sdk.post('rooms.join', { rid }); // then retry once
    return sdk.post('rooms.saveDraft', { rid, draft, tmid });
  }
  throw e;
}

Prevention

When it happens

Trigger: POST rooms.saveDraft with a rid the authenticated user is not subscribed to: never-joined public channel, private channel they were removed from, another user's DM room, or a rid typo.

Common situations: Bots writing drafts into channels they only have permission to read but never joined; users removed from a private room whose stale client still syncs drafts; state restoration tools replaying drafts onto a fresh account; room ids confused between threads and channels.

Related errors


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

Appendix: source

Thrown at apps/meteor/server/api/v1/rooms.ts:467

	{
		authRequired: true,
		body: saveDraftBodySchema,
		response: {
			200: saveDraftResponseSchema,
			400: validateBadRequestErrorResponse,
			401: validateUnauthorizedErrorResponse,
		},
	},
	async function action() {
		const { rid, draft, tmid } = this.bodyParams;

		if (draft.length > (settings.get<number>('Message_MaxAllowedSize') ?? 0)) {
			return API.v1.failure('error-message-size-exceeded');
		}

		const subscription = await Subscriptions.updateDraftByRoomIdAndUserId(rid, this.userId, draft || undefined, tmid);
		if (!subscription) {
			throw new Meteor.Error('error-invalid-subscription', 'Invalid subscription');
		}

		void notifyOnSubscriptionChanged(subscription);

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

API.v1.post(
	'rooms.cleanHistory',
	{
		authRequired: true,
		body: isRoomsCleanHistoryProps,
		response: {
			200: ajv.compile<{ _id: string; count: number }>({
				type: 'object',
				properties: {
					_id: { type: 'string' },

View on GitHub (pinned to e4b8178b20)