{"record":{"id":"92d4779e8eb3ddf2","repo":"RocketChat/Rocket.Chat","slug":"error-transcript-not-requested","errorCode":null,"errorMessage":"error-transcript-not-requested","messagePattern":"error-transcript-not-requested","errorType":"exception","errorClass":"Error","httpStatus":400,"severity":"error","filePath":"apps/meteor/server/api/v1/omnichannel/transcript.ts","lineNumber":45,"sourceCode":"\t{\n\t\tauthRequired: true,\n\t\tpermissionsRequired: ['send-omnichannel-chat-transcript'],\n\t\tvalidateParams: {\n\t\t\tPOST: isPOSTLivechatTranscriptRequestParams,\n\t\t},\n\t},\n\t{\n\t\tasync delete() {\n\t\t\tconst { rid } = this.urlParams;\n\t\t\tconst room = await LivechatRooms.findOneById<Pick<IOmnichannelRoom, 'open' | 'transcriptRequest' | 'v'>>(rid, {\n\t\t\t\tprojection: { open: 1, transcriptRequest: 1, v: 1 },\n\t\t\t});\n\n\t\t\tif (!room?.open) {\n\t\t\t\tthrow new Error('error-invalid-room');\n\t\t\t}\n\t\t\tif (!room.transcriptRequest) {\n\t\t\t\tthrow new Error('error-transcript-not-requested');\n\t\t\t}\n\n\t\t\tif (!(await Omnichannel.isWithinMACLimit(room))) {\n\t\t\t\tthrow new Error('error-mac-limit-reached');\n\t\t\t}\n\n\t\t\tawait LivechatRooms.unsetEmailTranscriptRequestedByRoomId(rid);\n\n\t\t\treturn API.v1.success();\n\t\t},\n\t\tasync post() {\n\t\t\tconst { rid } = this.urlParams;\n\t\t\tconst { email, subject } = this.bodyParams;\n\n\t\t\tconst user = await Users.findOneById(this.userId, {\n\t\t\t\tprojection: { _id: 1, username: 1, name: 1, utcOffset: 1 },\n\t\t\t});\n","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/api/v1/omnichannel/transcript.ts#L27-L63","documentation":"Thrown by DELETE /api/v1/livechat/transcript/:rid when the room exists and is open but has no transcriptRequest field — i.e. nobody ever requested an emailed transcript for that room. The DELETE endpoint exists only to cancel a pending transcript request, so it refuses when there is nothing pending.","triggerScenarios":"Calling DELETE on an open room before anyone called POST /api/v1/livechat/transcript/:rid; calling DELETE twice (the first call unsets transcriptRequest, so the second hits this); the visitor requested the transcript via the widget and you then cancel it twice from an integration.","commonSituations":"Duplicate webhooks/cron jobs firing the cancel twice; UI allowing the 'cancel transcript' button when no request was made because state was not refreshed after the first cancellation.","solutions":["Only call DELETE after a successful POST /api/v1/livechat/transcript/:rid on the same room","Treat a second cancellation as a no-op (catch this error and continue)","Make your cancel operation idempotent by tracking whether a request is outstanding per rid"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Track request state per rid in your client instead of relying on the server to remember\nif (transcriptRequestedByRid.has(rid)) {\n  await cancelTranscript(rid);\n  transcriptRequestedByRid.delete(rid);\n}","typeGuard":null,"tryCatchPattern":"const body = await (await fetch(`${server}/api/v1/livechat/transcript/${rid}`, { method: 'DELETE', headers })).json();\nif (!body.success && body.error === 'error-transcript-not-requested') { /* idempotent: nothing pending */ }","preventionTips":["Make cancel idempotent: swallow error-transcript-not-requested as success","Only expose 'cancel transcript' UI after a successful POST transcript"],"tags":["rest-api","livechat","transcript","state-machine","validation"],"backgroundTag":"invalid-state-transition","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}