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
- Join the room first (POST /api/v1/rooms.join or the subscription flow), then re-send the draft
- Verify membership with GET /api/v1/subscriptions.get?rid=... before saving
- Drop drafts for rooms the user left instead of erroring
- 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
- Sync drafts only for rooms present in the user's subscriptions
- Discard drafts on rooms the user left or was removed from
- Verify rid is a room id, not a thread/message id
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
- error-action-not-allowed
- error-cannot-delete-team-channel
- error-invalid-room
- error-invalid-subscription
- error-message-size-exceeded
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)